Home: collapse guide, 5 videos per row, hide Shorts. Watch: hide recommendations sidebar and center the player/content. Adds a topbar theme switcher that triggers YouTube's official Appearance modes.
// ==UserScript==
// @name YouTube Layout Plus
// @namespace https://qazwsx123.uk/
// @version 0.1.14
// @description Home: collapse guide, 5 videos per row, hide Shorts. Watch: hide recommendations sidebar and center the player/content. Adds a topbar theme switcher that triggers YouTube's official Appearance modes.
// @author Codex
// @match https://www.youtube.com/*
// @run-at document-idle
// @grant none
// ==/UserScript==
(function () {
'use strict';
const STYLE_ID = 'tm-youtube-layout-plus-style';
const THEME_SWITCHER_ID = 'tm-youtube-layout-plus-theme-switcher';
const THEME_STATUS_ID = 'tm-youtube-layout-plus-theme-status';
const THEME_BUSY_CLASS = 'tm-youtube-layout-plus-theme-busy';
const THEME_MODE_STORAGE_KEY = 'tm-youtube-layout-plus-theme-mode';
const THEME_MODES = [
{ mode: 'auto', label: 'Auto', prefF6: '80', signal: 'TOGGLE_DARK_THEME_DEVICE' },
{ mode: 'light', label: 'Light', prefF6: '80080', signal: 'TOGGLE_DARK_THEME_OFF' },
{ mode: 'dark', label: 'Dark', prefF6: '480', signal: 'TOGGLE_DARK_THEME_ON' },
];
let currentUrl = location.href;
let applyTimer = null;
let bodyObserver = null;
let routeTimer = null;
let lastAutoCollapsedHomeUrl = '';
let currentThemeMode = loadStoredThemeMode();
let themeSwitchInFlight = false;
let pendingThemeMode = null;
function isHomePage() {
return location.pathname === '/';
}
function isWatchPage() {
return location.pathname === '/watch';
}
function ensureStyle() {
if (document.getElementById(STYLE_ID)) return;
const style = document.createElement('style');
style.id = STYLE_ID;
style.textContent = `
html.tm-youtube-layout-plus-home {
--tm-guide-collapsed-width: 72px;
--tm-chip-overlay-shift: 18px;
--tm-chip-strip-bg:
radial-gradient(circle at 18% 0%, rgba(255, 255, 255, 0.42) 0%, rgba(255, 255, 255, 0) 34%),
radial-gradient(circle at 82% 6%, rgba(255, 255, 255, 0.22) 0%, rgba(255, 255, 255, 0) 28%),
linear-gradient(180deg, rgba(214, 223, 233, 0.32) 0%, rgba(191, 201, 212, 0.22) 100%);
--tm-chip-active-bg:
radial-gradient(circle at 20% 0%, rgba(255, 255, 255, 0.52) 0%, rgba(255, 255, 255, 0) 42%),
linear-gradient(180deg, rgba(250, 252, 255, 0.52) 0%, rgba(229, 236, 243, 0.28) 100%);
--tm-chip-text: #3b424b;
--tm-chip-text-active: #1f2328;
--tm-chip-separator: rgba(70, 77, 88, 0.18);
--tm-chip-strip-border: rgba(255, 255, 255, 0.24);
--tm-chip-strip-top-line: rgba(156, 164, 175, 0.32);
--tm-chip-strip-shadow-top: rgba(255, 255, 255, 0.24);
--tm-chip-strip-shadow-bottom: rgba(43, 50, 59, 0.12);
--tm-chip-hover-bg: rgba(255, 255, 255, 0.12);
--tm-chip-active-border: rgba(148, 157, 169, 0.22);
--tm-chip-active-top-stroke: rgba(148, 157, 169, 0.2);
--tm-chip-active-shadow-top: rgba(255, 255, 255, 0.4);
--tm-chip-active-shadow-bottom: rgba(148, 157, 169, 0.24);
--tm-chip-active-shadow-mid: rgba(60, 64, 67, 0.1);
--tm-chip-active-shadow-ring: rgba(118, 126, 137, 0.1);
--tm-chip-arrow-bg:
radial-gradient(circle at 20% 0%, rgba(255, 255, 255, 0.28) 0%, rgba(255, 255, 255, 0) 38%),
linear-gradient(180deg, rgba(248, 251, 255, 0.28) 0%, rgba(228, 235, 242, 0.18) 100%);
--tm-chip-arrow-shadow-top: rgba(255, 255, 255, 0.18);
--tm-chip-arrow-shadow-mid: rgba(60, 64, 67, 0.08);
--tm-chip-glass-blur: 34px;
}
html[dark].tm-youtube-layout-plus-home {
--tm-chip-strip-bg:
radial-gradient(circle at 18% 0%, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0) 34%),
linear-gradient(180deg, rgba(46, 51, 58, 0.52) 0%, rgba(28, 31, 37, 0.38) 100%);
--tm-chip-active-bg:
radial-gradient(circle at 20% 0%, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0) 40%),
linear-gradient(180deg, rgba(86, 92, 100, 0.38) 0%, rgba(61, 66, 73, 0.26) 100%);
--tm-chip-text: rgba(231, 234, 237, 0.9);
--tm-chip-text-active: #ffffff;
--tm-chip-separator: rgba(255, 255, 255, 0.1);
--tm-chip-strip-border: rgba(255, 255, 255, 0.1);
--tm-chip-strip-top-line: rgba(255, 255, 255, 0.12);
--tm-chip-strip-shadow-top: rgba(255, 255, 255, 0.08);
--tm-chip-strip-shadow-bottom: rgba(0, 0, 0, 0.28);
--tm-chip-hover-bg: rgba(255, 255, 255, 0.08);
--tm-chip-active-border: rgba(255, 255, 255, 0.12);
--tm-chip-active-top-stroke: rgba(255, 255, 255, 0.08);
--tm-chip-active-shadow-top: rgba(255, 255, 255, 0.06);
--tm-chip-active-shadow-bottom: rgba(255, 255, 255, 0.04);
--tm-chip-active-shadow-mid: rgba(0, 0, 0, 0.18);
--tm-chip-active-shadow-ring: rgba(255, 255, 255, 0.06);
--tm-chip-arrow-bg:
radial-gradient(circle at 20% 0%, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0) 38%),
linear-gradient(180deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.06) 100%);
--tm-chip-arrow-shadow-top: rgba(255, 255, 255, 0.06);
--tm-chip-arrow-shadow-mid: rgba(0, 0, 0, 0.2);
--tm-chip-glass-blur: 28px;
}
html.${THEME_BUSY_CLASS} ytd-popup-container tp-yt-iron-dropdown {
visibility: hidden !important;
}
#${THEME_SWITCHER_ID} {
position: relative;
display: flex;
align-items: center;
gap: 0;
margin-left: 12px;
padding: 4px 6px;
border: 1px solid var(--yt-spec-10-percent-layer, rgba(0, 0, 0, 0.12));
border-radius: 20px;
background: var(--yt-spec-badge-chip-background, rgba(0, 0, 0, 0.05));
}
#${THEME_STATUS_ID} {
position: fixed;
top: 72px;
right: 24px;
z-index: 2200;
max-width: 320px;
padding: 10px 14px;
border: 1px solid rgba(190, 60, 60, 0.28);
border-radius: 14px;
background: rgba(126, 28, 28, 0.92);
color: #fff;
font: 500 13px/1.4 Roboto, Arial, sans-serif;
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.24);
opacity: 0;
transform: translateY(-6px);
pointer-events: none;
transition: opacity 160ms ease, transform 160ms ease;
}
#${THEME_STATUS_ID}[data-visible="true"] {
opacity: 1;
transform: translateY(0);
}
#${THEME_SWITCHER_ID} .tm-theme-switch-track {
display: inline-flex;
align-items: center;
gap: 4px;
}
#${THEME_SWITCHER_ID} .tm-theme-switch-option {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 52px;
height: 28px;
padding: 0 10px;
border: 0;
border-radius: 14px;
background: transparent;
color: var(--yt-spec-text-primary, #0f0f0f);
cursor: pointer;
font: 600 12px/1 Roboto, Arial, sans-serif;
transition: background 140ms ease, color 140ms ease, box-shadow 140ms ease;
}
#${THEME_SWITCHER_ID} .tm-theme-switch-option:hover {
background: var(--yt-spec-badge-chip-background-hover, rgba(0, 0, 0, 0.08));
}
#${THEME_SWITCHER_ID} .tm-theme-switch-option[data-active="true"] {
background: var(--yt-spec-brand-background-solid, rgba(15, 15, 15, 0.92));
color: var(--yt-spec-static-brand-white, #fff);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
}
html[dark] #${THEME_SWITCHER_ID} {
border-color: rgba(255, 255, 255, 0.14);
background: rgba(255, 255, 255, 0.04);
}
html[dark] #${THEME_SWITCHER_ID} .tm-theme-switch-option {
color: rgba(255, 255, 255, 0.92);
}
html[dark] #${THEME_SWITCHER_ID} .tm-theme-switch-option[data-active="true"] {
background: rgba(255, 255, 255, 0.14);
color: #fff;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.32);
}
html.tm-youtube-layout-plus-home ytd-app[mini-guide-visible]:not([guide-persistent-and-visible]) ytd-feed-filter-chip-bar-renderer,
html.tm-youtube-layout-plus-home ytd-app[mini-guide-visible]:not([guide-persistent-and-visible]) #chips-wrapper {
left: var(--tm-guide-collapsed-width) !important;
width: calc(100vw - var(--tm-guide-collapsed-width)) !important;
}
html.tm-youtube-layout-plus-home ytd-feed-filter-chip-bar-renderer {
padding: 0 14px !important;
background: transparent !important;
}
html.tm-youtube-layout-plus-home ytd-feed-filter-chip-bar-renderer #chips-wrapper,
html.tm-youtube-layout-plus-home ytd-feed-filter-chip-bar-renderer #chips {
align-items: center !important;
}
html.tm-youtube-layout-plus-home ytd-feed-filter-chip-bar-renderer #chips-wrapper {
transform: translateY(var(--tm-chip-overlay-shift)) !important;
}
html.tm-youtube-layout-plus-home ytd-feed-filter-chip-bar-renderer #chips-content {
position: relative !important;
overflow: hidden !important;
isolation: isolate !important;
padding: 5px 8px !important;
margin-top: 0 !important;
border: 1px solid var(--tm-chip-strip-border) !important;
border-radius: 22px !important;
background: var(--tm-chip-strip-bg) !important;
backdrop-filter: blur(var(--tm-chip-glass-blur)) saturate(220%) contrast(1.05) brightness(1.08) !important;
-webkit-backdrop-filter: blur(var(--tm-chip-glass-blur)) saturate(220%) contrast(1.05) brightness(1.08) !important;
box-shadow:
inset 0 1px 0 var(--tm-chip-strip-top-line),
inset 0 1px 0 var(--tm-chip-strip-shadow-top),
0 2px 0 rgba(34, 40, 49, 0.11),
0 3px 2px rgba(34, 40, 49, 0.06),
0 5px 7px rgba(34, 40, 49, 0.03) !important;
}
html.tm-youtube-layout-plus-home ytd-feed-filter-chip-bar-renderer #chips-content::before {
content: '' !important;
position: absolute !important;
inset: 0 !important;
border-radius: inherit !important;
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.26) 0%, rgba(255, 255, 255, 0.08) 36%, rgba(255, 255, 255, 0.02) 100%) !important;
pointer-events: none !important;
z-index: 0 !important;
}
html.tm-youtube-layout-plus-home ytd-feed-filter-chip-bar-renderer #chips-content::after {
content: '' !important;
position: absolute !important;
inset: 0 !important;
border-radius: inherit !important;
background:
radial-gradient(circle at 50% 100%, rgba(180, 198, 220, 0.12) 0%, rgba(180, 198, 220, 0) 58%) !important;
pointer-events: none !important;
z-index: 0 !important;
}
html.tm-youtube-layout-plus-home ytd-feed-filter-chip-bar-renderer yt-chip-cloud-chip-renderer {
position: relative !important;
z-index: 1 !important;
margin: 0 !important;
padding: 0 2px !important;
}
html.tm-youtube-layout-plus-home ytd-feed-filter-chip-bar-renderer yt-chip-cloud-chip-renderer + yt-chip-cloud-chip-renderer::before {
content: '' !important;
position: absolute !important;
left: -1px !important;
top: 9px !important;
bottom: 9px !important;
width: 1px !important;
border-radius: 999px !important;
background: var(--tm-chip-separator) !important;
pointer-events: none !important;
}
html.tm-youtube-layout-plus-home ytd-feed-filter-chip-bar-renderer yt-chip-cloud-chip-renderer button.ytChipShapeButtonReset {
border-radius: 18px !important;
overflow: visible !important;
}
html.tm-youtube-layout-plus-home ytd-feed-filter-chip-bar-renderer yt-chip-cloud-chip-renderer .ytChipShapeChip {
position: relative !important;
min-height: 38px !important;
padding: 0 22px !important;
border: 0 !important;
border-radius: 18px !important;
background: transparent !important;
box-shadow: none !important;
color: var(--tm-chip-text) !important;
font-weight: 600 !important;
letter-spacing: -0.01em !important;
transition:
background 140ms ease,
box-shadow 140ms ease,
color 140ms ease,
transform 140ms ease,
backdrop-filter 140ms ease !important;
backdrop-filter: blur(calc(var(--tm-chip-glass-blur) * 0.7)) saturate(170%) brightness(1.04) !important;
-webkit-backdrop-filter: blur(calc(var(--tm-chip-glass-blur) * 0.7)) saturate(170%) brightness(1.04) !important;
}
html.tm-youtube-layout-plus-home ytd-feed-filter-chip-bar-renderer yt-chip-cloud-chip-renderer .ytChipShapeChip > div {
font-size: 15px !important;
line-height: 36px !important;
}
html.tm-youtube-layout-plus-home ytd-feed-filter-chip-bar-renderer yt-chip-cloud-chip-renderer:hover .ytChipShapeChip,
html.tm-youtube-layout-plus-home ytd-feed-filter-chip-bar-renderer yt-chip-cloud-chip-renderer button:focus-visible .ytChipShapeChip {
background: var(--tm-chip-hover-bg) !important;
color: var(--tm-chip-text-active) !important;
}
html.tm-youtube-layout-plus-home ytd-feed-filter-chip-bar-renderer yt-chip-cloud-chip-renderer[selected] .ytChipShapeChip,
html.tm-youtube-layout-plus-home ytd-feed-filter-chip-bar-renderer yt-chip-cloud-chip-renderer .ytChipShapeChip.ytChipShapeActive {
background: var(--tm-chip-active-bg) !important;
border: 1px solid var(--tm-chip-active-border) !important;
box-shadow: none !important;
color: var(--tm-chip-text-active) !important;
transform: none !important;
z-index: 1 !important;
}
html.tm-youtube-layout-plus-home ytd-feed-filter-chip-bar-renderer yt-chip-cloud-chip-renderer[selected]::before,
html.tm-youtube-layout-plus-home ytd-feed-filter-chip-bar-renderer yt-chip-cloud-chip-renderer[selected] + yt-chip-cloud-chip-renderer::before {
opacity: 0 !important;
}
html.tm-youtube-layout-plus-home ytd-feed-filter-chip-bar-renderer yt-chip-cloud-chip-renderer yt-touch-feedback-shape,
html.tm-youtube-layout-plus-home ytd-feed-filter-chip-bar-renderer yt-chip-cloud-chip-renderer .yt-spec-touch-feedback-shape__stroke,
html.tm-youtube-layout-plus-home ytd-feed-filter-chip-bar-renderer yt-chip-cloud-chip-renderer .yt-spec-touch-feedback-shape__fill {
border-radius: 18px !important;
}
html.tm-youtube-layout-plus-home ytd-feed-filter-chip-bar-renderer #right-arrow.ytd-feed-filter-chip-bar-renderer,
html.tm-youtube-layout-plus-home ytd-feed-filter-chip-bar-renderer #left-arrow.ytd-feed-filter-chip-bar-renderer {
margin-top: 0 !important;
transform: none !important;
}
html.tm-youtube-layout-plus-home ytd-feed-filter-chip-bar-renderer #right-arrow button,
html.tm-youtube-layout-plus-home ytd-feed-filter-chip-bar-renderer #left-arrow button {
border: 0 !important;
border-radius: 18px !important;
background: var(--tm-chip-arrow-bg) !important;
transform: none !important;
backdrop-filter: blur(calc(var(--tm-chip-glass-blur) * 0.72)) saturate(175%) brightness(1.04) !important;
-webkit-backdrop-filter: blur(calc(var(--tm-chip-glass-blur) * 0.72)) saturate(175%) brightness(1.04) !important;
box-shadow:
inset 0 1px 0 var(--tm-chip-arrow-shadow-top),
0 4px 12px var(--tm-chip-arrow-shadow-mid) !important;
}
html.tm-youtube-layout-plus-home ytd-app[mini-guide-visible]:not([guide-persistent-and-visible]) ytd-page-manager {
margin-left: var(--tm-guide-collapsed-width) !important;
width: calc(100% - var(--tm-guide-collapsed-width)) !important;
}
html.tm-youtube-layout-plus-home ytd-browse {
overflow: visible !important;
}
html.tm-youtube-layout-plus-home ytd-app[mini-guide-visible]:not([guide-persistent-and-visible]) ytd-two-column-browse-results-renderer,
html.tm-youtube-layout-plus-home ytd-app[mini-guide-visible]:not([guide-persistent-and-visible]) ytd-rich-grid-renderer,
html.tm-youtube-layout-plus-home ytd-app[mini-guide-visible]:not([guide-persistent-and-visible]) #contents.ytd-rich-grid-renderer {
margin-left: 0 !important;
width: auto !important;
}
html.tm-youtube-layout-plus-home #contents.ytd-rich-grid-renderer {
margin-top: 0 !important;
padding-top: calc(var(--tm-chip-overlay-shift) + 8px) !important;
padding-left: 14px !important;
padding-right: 12px !important;
box-sizing: border-box !important;
}
html.tm-youtube-layout-plus-home ytd-rich-grid-renderer {
--ytd-rich-grid-items-per-row: 5 !important;
--ytd-rich-grid-posts-per-row: 5 !important;
}
html.tm-youtube-layout-plus-home ytd-rich-item-renderer,
html.tm-youtube-layout-plus-home ytd-rich-grid-row,
html.tm-youtube-layout-plus-home ytd-rich-grid-media,
html.tm-youtube-layout-plus-home #contents.ytd-rich-grid-renderer > * {
max-width: none !important;
}
html.tm-youtube-layout-plus-home ytd-rich-item-renderer {
position: relative !important;
z-index: 0 !important;
margin-left: 0 !important;
margin-right: 10px !important;
transform-origin: center top !important;
transition:
transform 160ms ease,
box-shadow 160ms ease,
z-index 160ms ease !important;
will-change: transform !important;
}
html.tm-youtube-layout-plus-home ytd-rich-item-renderer:hover,
html.tm-youtube-layout-plus-home ytd-rich-item-renderer:focus-within {
transform: scale(1.1) !important;
z-index: 4 !important;
}
html.tm-youtube-layout-plus-home ytd-rich-section-renderer,
html.tm-youtube-layout-plus-home ytd-rich-shelf-renderer,
html.tm-youtube-layout-plus-home ytd-reel-shelf-renderer {
display: none !important;
}
html.tm-youtube-layout-plus-watch ytd-watch-flexy #secondary,
html.tm-youtube-layout-plus-watch ytd-watch-flexy #related,
html.tm-youtube-layout-plus-watch ytd-watch-flexy #secondary-inner,
html.tm-youtube-layout-plus-watch ytd-watch-next-secondary-results-renderer {
display: none !important;
}
html.tm-youtube-layout-plus-watch ytd-watch-flexy[is-two-columns_] #primary {
max-width: none !important;
width: min(1400px, calc(100vw - 48px)) !important;
margin: 0 auto !important;
}
html.tm-youtube-layout-plus-watch ytd-watch-flexy[is-two-columns_] #columns {
display: block !important;
}
html.tm-youtube-layout-plus-watch ytd-watch-flexy[is-two-columns_] #primary-inner,
html.tm-youtube-layout-plus-watch ytd-watch-flexy[is-two-columns_] #above-the-fold,
html.tm-youtube-layout-plus-watch ytd-watch-flexy[is-two-columns_] #below {
max-width: none !important;
}
`;
document.head.appendChild(style);
}
function collapseGuide() {
const app = document.querySelector('ytd-app');
if (app) {
app.removeAttribute('guide-persistent-and-visible');
app.setAttribute('mini-guide-visible', '');
}
const drawer = document.querySelector('tp-yt-app-drawer#guide');
if (drawer) {
drawer.removeAttribute('opened');
drawer.style.width = '';
drawer.style.minWidth = '';
drawer.style.visibility = '';
}
const guideRenderer = document.querySelector('ytd-guide-renderer');
if (guideRenderer) {
guideRenderer.style.display = '';
guideRenderer.style.width = '';
guideRenderer.style.minWidth = '';
}
}
function applyHomeLayout() {
const richGrid = document.querySelector('ytd-rich-grid-renderer');
if (!richGrid) return;
document.documentElement.classList.add('tm-youtube-layout-plus-home');
if (lastAutoCollapsedHomeUrl !== location.href) {
collapseGuide();
lastAutoCollapsedHomeUrl = location.href;
}
}
function applyWatchLayout() {
const watchFlexy = document.querySelector('ytd-watch-flexy');
if (!watchFlexy) return;
document.documentElement.classList.add('tm-youtube-layout-plus-watch');
}
function clearModeClasses() {
document.documentElement.classList.remove(
'tm-youtube-layout-plus-home',
'tm-youtube-layout-plus-watch'
);
}
function loadStoredThemeMode() {
try {
const mode = window.localStorage.getItem(THEME_MODE_STORAGE_KEY);
return THEME_MODES.some((item) => item.mode === mode) ? mode : null;
} catch {
return null;
}
}
function persistThemeMode(mode) {
try {
if (THEME_MODES.some((item) => item.mode === mode)) {
window.localStorage.setItem(THEME_MODE_STORAGE_KEY, mode);
} else {
window.localStorage.removeItem(THEME_MODE_STORAGE_KEY);
}
} catch {
// Ignore storage failures.
}
}
function delay(ms) {
return new Promise((resolve) => {
window.setTimeout(resolve, ms);
});
}
function getThemePrefValue(mode) {
return THEME_MODES.find((item) => item.mode === mode)?.prefF6 || null;
}
function getThemeSignal(mode) {
return THEME_MODES.find((item) => item.mode === mode)?.signal || null;
}
function getThemeOptionSignal(option) {
return option?.data?.serviceEndpoint?.signalServiceEndpoint?.actions?.[0]?.signalAction?.signal || null;
}
function getThemeModeFromPrefCookie() {
const prefEntries = document.cookie
.split('; ')
.filter((entry) => entry.startsWith('PREF='));
const lastEntry = prefEntries[prefEntries.length - 1];
if (!lastEntry) return null;
try {
const pref = decodeURIComponent(lastEntry.slice(5));
const f6Match = pref.match(/(?:^|&)f6=(\d+)/);
if (!f6Match) return null;
const f6Value = f6Match[1];
if (/80080$/.test(f6Value)) return 'light';
if (/480$/.test(f6Value)) return 'dark';
if (/80$/.test(f6Value)) return 'auto';
return 'auto';
} catch {
return null;
}
}
function inferThemeModeFromDocument() {
return document.documentElement.hasAttribute('dark') ? 'dark' : 'light';
}
function getObservedThemeMode() {
return getThemeModeFromPrefCookie()
|| detectThemeModeFromOptions()
|| inferThemeModeFromDocument();
}
function showThemeStatusMessage(message) {
let status = document.getElementById(THEME_STATUS_ID);
if (!status) {
status = document.createElement('div');
status.id = THEME_STATUS_ID;
status.dataset.visible = 'false';
document.body.appendChild(status);
}
status.textContent = message;
status.dataset.visible = 'true';
window.clearTimeout(showThemeStatusMessage.hideTimer);
showThemeStatusMessage.hideTimer = window.setTimeout(() => {
status.dataset.visible = 'false';
}, 2600);
}
function hideThemeStatusMessage() {
const status = document.getElementById(THEME_STATUS_ID);
if (!status) return;
status.dataset.visible = 'false';
window.clearTimeout(showThemeStatusMessage.hideTimer);
}
function isVisibleElement(element) {
if (!element) return false;
const rect = element.getBoundingClientRect();
return rect.width > 0 && rect.height > 0;
}
function getOfficialThemeOptions(options = {}) {
const { visibleOnly = false } = options;
return Array.from(document.querySelectorAll('ytd-compact-link-renderer')).filter(
(option) => THEME_MODES.some((mode) => getThemeOptionSignal(option) === mode.signal)
&& (!visibleOnly || isVisibleElement(option))
);
}
function detectThemeModeFromOptions(options = getOfficialThemeOptions({ visibleOnly: true })) {
const selectedOption = options.find((option) => option?.data?.icon?.iconType === 'CHECK');
if (!selectedOption) return null;
return THEME_MODES.find((mode) => mode.signal === getThemeOptionSignal(selectedOption))?.mode || null;
}
function findAccountMenuButton() {
return document.querySelector('button#avatar-btn')
|| Array.from(document.querySelectorAll('ytd-topbar-menu-button-renderer button, button')).find((button) => {
if (button.id === 'avatar-btn') return true;
if (button.querySelector('img') && button.getAttribute('aria-haspopup') === 'true') return true;
return false;
})
|| null;
}
function findAppearanceMenuEntry() {
return Array.from(document.querySelectorAll('ytd-toggle-theme-compact-link-renderer')).find(
(entry) => isVisibleElement(entry)
) || null;
}
function closeOfficialThemeMenus() {
document.dispatchEvent(
new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, cancelable: true })
);
document.dispatchEvent(
new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, cancelable: true })
);
}
async function waitFor(getValue, timeout = 2200) {
const startedAt = Date.now();
while (Date.now() - startedAt < timeout) {
const value = getValue();
if (value) return value;
await delay(50);
}
return null;
}
async function ensureOfficialThemeOptionsVisible() {
const existingOptions = getOfficialThemeOptions({ visibleOnly: true });
if (existingOptions.length) return existingOptions;
const existingAppearanceEntry = findAppearanceMenuEntry();
if (existingAppearanceEntry) {
existingAppearanceEntry.click();
return await waitFor(() => {
const options = getOfficialThemeOptions({ visibleOnly: true });
return options.length ? options : null;
});
}
closeOfficialThemeMenus();
await delay(80);
const accountMenuButton = findAccountMenuButton();
if (!accountMenuButton) return null;
accountMenuButton.click();
const appearanceEntry = await waitFor(findAppearanceMenuEntry);
if (!appearanceEntry) return null;
appearanceEntry.click();
return await waitFor(() => {
const options = getOfficialThemeOptions({ visibleOnly: true });
return options.length ? options : null;
});
}
function updateThemeSwitcherUI() {
const switcher = document.getElementById(THEME_SWITCHER_ID);
if (!switcher) return;
if (!pendingThemeMode) {
const observedMode = getObservedThemeMode();
if (observedMode) {
currentThemeMode = observedMode;
persistThemeMode(observedMode);
}
} else if (!currentThemeMode) {
currentThemeMode = loadStoredThemeMode()
|| inferThemeModeFromDocument();
}
const visibleMode = pendingThemeMode || currentThemeMode;
const optionButtons = switcher.querySelectorAll('.tm-theme-switch-option[data-mode]');
for (const button of optionButtons) {
const isActive = button.dataset.mode === visibleMode;
button.dataset.active = String(isActive);
button.setAttribute('aria-pressed', String(isActive));
button.disabled = themeSwitchInFlight;
}
}
async function verifyThemeMode(mode) {
const expectedDark = mode === 'dark'
|| (mode === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches);
let lastResult = null;
for (let attempt = 0; attempt < 3; attempt += 1) {
const options = await ensureOfficialThemeOptionsVisible();
const menuMode = options?.length ? detectThemeModeFromOptions(options) : null;
const cookieMode = getThemeModeFromPrefCookie();
const hasDark = document.documentElement.hasAttribute('dark');
const actualMode = menuMode || cookieMode || inferThemeModeFromDocument();
const menuConsistent = !menuMode || menuMode === mode;
const cookieConsistent = !cookieMode || cookieMode === mode;
const darkConsistent = hasDark === expectedDark;
lastResult = {
ok: menuConsistent && cookieConsistent && darkConsistent,
actualMode,
menuMode,
cookieMode,
hasDark,
};
if (lastResult.ok) {
closeOfficialThemeMenus();
return lastResult;
}
closeOfficialThemeMenus();
await delay(180);
}
return lastResult || {
ok: false,
actualMode: getObservedThemeMode(),
menuMode: null,
cookieMode: getThemeModeFromPrefCookie(),
hasDark: document.documentElement.hasAttribute('dark'),
};
}
async function setThemeMode(mode) {
if (themeSwitchInFlight) return;
if (!THEME_MODES.some((item) => item.mode === mode)) return;
const previousMode = currentThemeMode
|| loadStoredThemeMode()
|| getThemeModeFromPrefCookie()
|| detectThemeModeFromOptions()
|| (document.documentElement.hasAttribute('dark') ? 'dark' : 'light');
if (previousMode === mode) {
currentThemeMode = mode;
persistThemeMode(mode);
pendingThemeMode = null;
updateThemeSwitcherUI();
return;
}
themeSwitchInFlight = true;
document.documentElement.classList.add(THEME_BUSY_CLASS);
pendingThemeMode = mode;
try {
updateThemeSwitcherUI();
const options = await ensureOfficialThemeOptionsVisible();
if (!options?.length) {
currentThemeMode = previousMode;
persistThemeMode(previousMode);
updateThemeSwitcherUI();
return;
}
const visibleMode = detectThemeModeFromOptions(options);
let didRequestOfficialMode = visibleMode === mode;
if (visibleMode !== mode) {
const targetOption = options.find((option) => getThemeOptionSignal(option) === getThemeSignal(mode));
if (!targetOption) {
currentThemeMode = previousMode;
persistThemeMode(previousMode);
updateThemeSwitcherUI();
return;
}
targetOption.click();
didRequestOfficialMode = true;
}
// Once the official menu action is triggered, trust the requested mode for the switcher UI.
currentThemeMode = mode;
persistThemeMode(mode);
updateThemeSwitcherUI();
const verification = await verifyThemeMode(mode);
if (!verification.ok) {
currentThemeMode = verification.actualMode || previousMode;
persistThemeMode(currentThemeMode);
pendingThemeMode = null;
updateThemeSwitcherUI();
showThemeStatusMessage(
`Theme switch failed: expected ${mode}, actual ${currentThemeMode || 'unknown'}.`
);
return;
}
currentThemeMode = verification.actualMode || mode;
persistThemeMode(currentThemeMode);
hideThemeStatusMessage();
} finally {
closeOfficialThemeMenus();
themeSwitchInFlight = false;
document.documentElement.classList.remove(THEME_BUSY_CLASS);
pendingThemeMode = null;
currentThemeMode = currentThemeMode || loadStoredThemeMode() || getThemeModeFromPrefCookie() || previousMode;
updateThemeSwitcherUI();
}
}
function ensureThemeSwitcher() {
const logoRenderer = document.querySelector('ytd-topbar-logo-renderer');
if (!logoRenderer) return;
let switcher = document.getElementById(THEME_SWITCHER_ID);
if (!switcher) {
const track = document.createElement('div');
track.className = 'tm-theme-switch-track';
track.setAttribute('role', 'group');
track.setAttribute('aria-label', 'Theme switcher');
switcher = document.createElement('div');
switcher.id = THEME_SWITCHER_ID;
for (const item of THEME_MODES) {
const optionButton = document.createElement('button');
optionButton.type = 'button';
optionButton.className = 'tm-theme-switch-option';
optionButton.dataset.mode = item.mode;
optionButton.dataset.active = 'false';
optionButton.textContent = item.label;
optionButton.setAttribute('aria-pressed', 'false');
optionButton.addEventListener('click', async (event) => {
event.preventDefault();
event.stopPropagation();
await setThemeMode(item.mode);
});
track.appendChild(optionButton);
}
switcher.append(track);
logoRenderer.insertAdjacentElement('afterend', switcher);
} else if (switcher.previousElementSibling !== logoRenderer) {
logoRenderer.insertAdjacentElement('afterend', switcher);
}
updateThemeSwitcherUI();
}
function applyLayout() {
ensureStyle();
clearModeClasses();
try {
ensureThemeSwitcher();
} catch (error) {
console.error('YouTube Layout Plus theme switcher failed:', error);
}
if (isHomePage()) {
applyHomeLayout();
return;
}
if (isWatchPage()) {
applyWatchLayout();
}
}
function scheduleApply() {
clearTimeout(applyTimer);
applyTimer = window.setTimeout(applyLayout, 120);
}
function startObservers() {
if (bodyObserver) return;
bodyObserver = new MutationObserver(() => {
scheduleApply();
});
bodyObserver.observe(document.body, {
childList: true,
subtree: true,
});
}
function watchRoute() {
if (routeTimer) return;
routeTimer = window.setInterval(() => {
if (location.href === currentUrl) return;
currentUrl = location.href;
if (!isHomePage()) {
lastAutoCollapsedHomeUrl = '';
}
scheduleApply();
}, 500);
}
function init() {
ensureStyle();
scheduleApply();
startObservers();
watchRoute();
}
init();
})();