Greasy Fork is available in English.
Home: collapse guide, 5 videos per row, hide Shorts. Watch: hide recommendations sidebar and center the player/content, while preserving live chat on streams/replays. Adds a topbar theme switcher that triggers YouTube's official Appearance modes via direct yt-action dispatch.
当前为
// ==UserScript==
// @name YouTube Layout Plus
// @namespace https://qazwsx123.uk/
// @version 0.2.17
// @description Home: collapse guide, 5 videos per row, hide Shorts. Watch: hide recommendations sidebar and center the player/content, while preserving live chat on streams/replays. Adds a topbar theme switcher that triggers YouTube's official Appearance modes via direct yt-action dispatch.
// @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 HOME_GRID_SWITCHER_ID = 'tm-youtube-layout-plus-home-grid-switcher';
const THEME_STATUS_ID = 'tm-youtube-layout-plus-theme-status';
const REPLAY_HIDE_CLASS = 'tm-youtube-layout-plus-hide-replay-prompt';
const THEME_MODE_STORAGE_KEY = 'tm-youtube-layout-plus-theme-mode';
const HOME_GRID_COLUMNS_STORAGE_KEY = 'tm-youtube-layout-plus-home-grid-columns';
const THEME_MODES = [
{ mode: 'auto', label: 'Auto', actionName: 'yt-signal-action-toggle-dark-theme-device' },
{ mode: 'light', label: 'Light', actionName: 'yt-signal-action-toggle-dark-theme-off' },
{ mode: 'dark', label: 'Dark', actionName: 'yt-signal-action-toggle-dark-theme-on' },
];
const HOME_GRID_COLUMN_OPTIONS = [4, 5];
const LAYOUT_MODE_CLASSES = [
'tm-youtube-layout-plus-home',
'tm-youtube-layout-plus-watch',
'tm-youtube-layout-plus-watch-chat',
'tm-youtube-layout-plus-watch-plain',
];
const OBSERVER_APPLY_DEBOUNCE_MS = 150;
const ROUTE_POLL_INTERVAL_MS = 3000;
// YouTube's PREF cookie f6 is a hex bitfield.
// 0x80 = "theme preference set" (device theme if no further bits).
// 0x400 = dark. 0x80000 = light.
const PREF_F6_PREF_BIT = 0x80;
const PREF_F6_DARK_BIT = 0x400;
const PREF_F6_LIGHT_BIT = 0x80000;
let currentUrl = location.href;
let applyFrame = 0;
let applyDelayTimer = null;
let bodyObserver = null;
let routeTimer = null;
let resizeTimer = null;
let resizeFrame = 0;
let watchLayoutRetryTimer = null;
let watchLayoutRetryStep = 0;
let watchChatStateTimer = null;
let lastWatchChatExpanded = null;
let playerSyncTimer = null;
let lastPlayerSyncKey = '';
let lastAutoCollapsedHomeUrl = '';
let lastAutoCollapsedWatchChatKey = '';
let currentThemeMode = loadStoredThemeMode();
let currentHomeGridColumns = loadStoredHomeGridColumns();
let themeSwitchInFlight = false;
let pendingThemeMode = null;
let themeStatusHideTimer = null;
let lastRenderedThemeMode = null;
let lastRenderedThemeDisabled = null;
let lastRenderedHomeGridColumns = null;
let lastPersistedThemeMode = currentThemeMode;
const WATCH_LAYOUT_RETRY_DELAYS = [0, 40, 120, 260, 520, 900, 1400, 2200, 3200, 4500];
function isHomePage() {
return location.pathname === '/';
}
function isWatchPage() {
return location.pathname === '/watch';
}
function getWatchPageKey() {
if (!isWatchPage()) return location.pathname;
const params = new URLSearchParams(location.search);
const videoId = params.get('v');
return videoId ? `/watch?v=${videoId}` : location.pathname;
}
function getWatchTeaserText() {
const teaserCarousel = document.querySelector('ytd-watch-metadata #teaser-carousel');
return (teaserCarousel?.innerText || teaserCarousel?.textContent || '').trim();
}
function hasLiveWatchPrompt(teaserText = getWatchTeaserText()) {
return /join the conversation to interact with the creator and others watching this live stream/i.test(teaserText)
|| (/watching now/i.test(document.body.innerText) && /started streaming/i.test(document.body.innerText));
}
function hasReplayWatchPrompt(teaserText = getWatchTeaserText()) {
return /live chat replay/i.test(teaserText)
|| /see what others said about this video while it was live/i.test(teaserText);
}
function hasWatchSidebarChat(watchFlexy) {
if (!watchFlexy) return false;
if (
watchFlexy.hasAttribute('is-live')
|| watchFlexy.hasAttribute('is-live-content')
|| watchFlexy.hasAttribute('live-chat-collapsed')
) {
return true;
}
const liveBadge = watchFlexy.querySelector(
'ytd-badge-supported-renderer[system-icons][icon="LIVE"], ' +
'ytd-thumbnail-overlay-time-status-renderer[overlay-style="LIVE"], ' +
'yt-icon[icon="yt-icons:live"]'
);
if (liveBadge && isVisibleElement(liveBadge)) {
return true;
}
const teaserText = getWatchTeaserText();
if (hasLiveWatchPrompt(teaserText)) {
return true;
}
const chatFrame = watchFlexy.querySelector(
'ytd-live-chat-frame#chat, #chat ytd-live-chat-frame, #chat-container ytd-live-chat-frame'
);
const chatHost = chatFrame?.closest('#chat') || watchFlexy.querySelector('#chat, #chat-container');
const hasStampedChat = watchFlexy.hasAttribute('should-stamp-chat') || !!chatFrame || !!chatHost;
if (hasReplayWatchPrompt(teaserText) && hasStampedChat) {
return true;
}
if (!chatFrame) return false;
if (chatFrame.hasAttribute('hidden')) return false;
if (chatFrame.hasAttribute('collapsed')) {
return hasStampedChat;
}
return isVisibleElement(chatFrame) || isVisibleElement(chatHost);
}
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;
}
.${REPLAY_HIDE_CLASS} {
display: none !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);
}
#${HOME_GRID_SWITCHER_ID} {
position: relative;
display: inline-flex;
align-items: center;
gap: 8px;
margin-left: 8px;
padding: 4px 8px;
border: 1px solid var(--yt-spec-10-percent-layer, rgba(0, 0, 0, 0.12));
border-radius: 999px;
background: var(--yt-spec-badge-chip-background, rgba(0, 0, 0, 0.05));
}
#${HOME_GRID_SWITCHER_ID} .tm-home-grid-option {
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border: 1px solid var(--yt-spec-10-percent-layer, rgba(0, 0, 0, 0.18));
border-radius: 999px;
background: transparent;
color: var(--yt-spec-text-primary, #0f0f0f);
cursor: pointer;
font: 700 12px/1 Roboto, Arial, sans-serif;
transition: background 140ms ease, color 140ms ease, border-color 140ms ease, box-shadow 140ms ease;
}
#${HOME_GRID_SWITCHER_ID} .tm-home-grid-option:hover {
background: var(--yt-spec-badge-chip-background-hover, rgba(0, 0, 0, 0.08));
}
#${HOME_GRID_SWITCHER_ID} .tm-home-grid-option[data-active="true"] {
border-color: transparent;
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] #${HOME_GRID_SWITCHER_ID} {
border-color: rgba(255, 255, 255, 0.14);
background: rgba(255, 255, 255, 0.04);
}
html[dark] #${HOME_GRID_SWITCHER_ID} .tm-home-grid-option {
border-color: rgba(255, 255, 255, 0.18);
color: rgba(255, 255, 255, 0.92);
}
html[dark] #${HOME_GRID_SWITCHER_ID} .tm-home-grid-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: var(--tm-home-grid-items-per-row, 5) !important;
--ytd-rich-grid-posts-per-row: var(--tm-home-grid-items-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,
html.tm-youtube-layout-plus-watch body,
html.tm-youtube-layout-plus-watch ytd-app,
html.tm-youtube-layout-plus-watch ytd-watch-flexy,
html.tm-youtube-layout-plus-watch ytd-watch-flexy #columns,
html.tm-youtube-layout-plus-watch ytd-watch-flexy #primary,
html.tm-youtube-layout-plus-watch ytd-watch-flexy #primary-inner,
html.tm-youtube-layout-plus-watch ytd-watch-flexy #below {
overflow-x: hidden !important;
overscroll-behavior-x: none !important;
max-width: 100% !important;
}
html.tm-youtube-layout-plus-watch-plain ytd-watch-flexy #secondary,
html.tm-youtube-layout-plus-watch-plain ytd-watch-flexy #related,
html.tm-youtube-layout-plus-watch-plain ytd-watch-flexy #secondary-inner,
html.tm-youtube-layout-plus-watch-plain ytd-watch-next-secondary-results-renderer,
html.tm-youtube-layout-plus-watch-chat ytd-watch-flexy #related,
html.tm-youtube-layout-plus-watch-chat ytd-watch-next-secondary-results-renderer {
display: none !important;
}
html.tm-youtube-layout-plus-watch-plain 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-plain ytd-watch-flexy[is-two-columns_] #columns {
display: block !important;
width: 100% !important;
max-width: 100% !important;
}
html.tm-youtube-layout-plus-watch-plain ytd-watch-flexy[is-two-columns_] #primary-inner,
html.tm-youtube-layout-plus-watch-plain ytd-watch-flexy[is-two-columns_] #above-the-fold,
html.tm-youtube-layout-plus-watch-plain ytd-watch-flexy[is-two-columns_] #below {
max-width: none !important;
}
html.tm-youtube-layout-plus-watch-chat-collapsed ytd-watch-flexy #secondary,
html.tm-youtube-layout-plus-watch-chat-collapsed ytd-watch-flexy #secondary-inner {
display: none !important;
}
html.tm-youtube-layout-plus-watch-chat-collapsed 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-chat-collapsed ytd-watch-flexy[is-two-columns_] #columns {
display: block !important;
width: 100% !important;
max-width: 100% !important;
}
html.tm-youtube-layout-plus-watch-chat-collapsed ytd-watch-flexy[is-two-columns_] #primary-inner,
html.tm-youtube-layout-plus-watch-chat-collapsed ytd-watch-flexy[is-two-columns_] #above-the-fold,
html.tm-youtube-layout-plus-watch-chat-collapsed 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 setLayoutModeClasses(targetClasses) {
const target = new Set(targetClasses);
const root = document.documentElement;
for (const cls of LAYOUT_MODE_CLASSES) {
const shouldHave = target.has(cls);
const hasIt = root.classList.contains(cls);
if (shouldHave && !hasIt) root.classList.add(cls);
else if (!shouldHave && hasIt) root.classList.remove(cls);
}
}
function applyHomeLayout() {
const richGrid = document.querySelector('ytd-rich-grid-renderer');
if (!richGrid) {
setLayoutModeClasses([]);
return;
}
setLayoutModeClasses(['tm-youtube-layout-plus-home']);
applyHomeGridColumns();
if (lastAutoCollapsedHomeUrl !== location.href) {
collapseGuide();
lastAutoCollapsedHomeUrl = location.href;
}
}
function applyWatchLayout() {
const watchFlexy = document.querySelector('ytd-watch-flexy');
if (!watchFlexy) {
setLayoutModeClasses([]);
return;
}
if (hasWatchSidebarChat(watchFlexy)) {
setLayoutModeClasses([
'tm-youtube-layout-plus-watch',
'tm-youtube-layout-plus-watch-chat',
]);
updateWatchChatPanelState(watchFlexy);
autoCollapseLiveChatPanel(watchFlexy);
syncWatchPlayerSize();
schedulePlayerSync(120);
return;
}
setLayoutModeClasses([
'tm-youtube-layout-plus-watch',
'tm-youtube-layout-plus-watch-plain',
]);
hideWatchReplayPrompt();
syncWatchPlayerSize();
schedulePlayerSync(120);
}
function clearHiddenReplayPrompts() {
for (const el of document.querySelectorAll('.' + REPLAY_HIDE_CLASS)) {
el.classList.remove(REPLAY_HIDE_CLASS);
}
}
function hideWatchReplayPrompt() {
const teaserCarousel = document.querySelector('ytd-watch-metadata #teaser-carousel');
if (!teaserCarousel) return;
const teaserText = getWatchTeaserText();
if (hasLiveWatchPrompt(teaserText)) {
teaserCarousel.classList.remove(REPLAY_HIDE_CLASS);
return;
}
const watchFlexy = document.querySelector('ytd-watch-flexy');
if (watchFlexy && hasReplayWatchPrompt(teaserText) && hasWatchSidebarChat(watchFlexy)) {
teaserCarousel.classList.remove(REPLAY_HIDE_CLASS);
return;
}
const replayPrompt = Array.from(
teaserCarousel.querySelectorAll('yt-video-metadata-carousel-view-model')
).find((card) => {
const text = (card.innerText || card.textContent || '').trim();
return /live chat replay/i.test(text)
|| /see what others said about this video while it was live/i.test(text);
});
if (!replayPrompt) {
teaserCarousel.classList.remove(REPLAY_HIDE_CLASS);
return;
}
teaserCarousel.classList.add(REPLAY_HIDE_CLASS);
}
function updateWatchChatPanelState(watchFlexy) {
const chatFrame = watchFlexy.querySelector('ytd-live-chat-frame#chat');
const openPanelButton = findOpenPanelButton();
const chatVisible = !!chatFrame && isVisibleElement(chatFrame);
const panelExpanded = chatVisible || !!(openPanelButton && openPanelButton.disabled);
document.documentElement.classList.toggle('tm-youtube-layout-plus-watch-chat-open', panelExpanded);
document.documentElement.classList.toggle('tm-youtube-layout-plus-watch-chat-collapsed', !panelExpanded);
if (lastWatchChatExpanded !== panelExpanded) {
lastWatchChatExpanded = panelExpanded;
window.requestAnimationFrame(() => {
syncWatchPlayerSize();
schedulePlayerSync(120);
});
}
}
function stopWatchChatStateSync() {
if (!watchChatStateTimer) return;
window.clearInterval(watchChatStateTimer);
watchChatStateTimer = null;
}
function startWatchChatStateSync() {
stopWatchChatStateSync();
if (!isWatchPage()) return;
watchChatStateTimer = window.setInterval(() => {
if (!isWatchPage()) {
stopWatchChatStateSync();
return;
}
const watchFlexy = document.querySelector('ytd-watch-flexy');
if (!watchFlexy) {
return;
}
const hasSidebarChat = hasWatchSidebarChat(watchFlexy);
const hasWatchChatClass = document.documentElement.classList.contains('tm-youtube-layout-plus-watch-chat');
if (hasSidebarChat && !hasWatchChatClass) {
scheduleApply();
return;
}
if (!hasSidebarChat || !hasWatchChatClass) {
return;
}
updateWatchChatPanelState(watchFlexy);
}, 500);
}
function findOpenPanelButton() {
return Array.from(document.querySelectorAll('button, yt-button-shape button, tp-yt-paper-button')).find(
(button) => /open panel/i.test((button.innerText || button.textContent || '').trim())
) || null;
}
function safeGetIframeDocument(frame) {
try {
return frame?.contentDocument || null;
} catch {
return null;
}
}
function autoCollapseLiveChatPanel(watchFlexy) {
const watchPageKey = getWatchPageKey();
if (lastAutoCollapsedWatchChatKey === watchPageKey) return;
const chatFrameHost = watchFlexy.querySelector('ytd-live-chat-frame#chat');
const openPanelButton = findOpenPanelButton();
if (openPanelButton && !openPanelButton.disabled) {
lastAutoCollapsedWatchChatKey = watchPageKey;
return;
}
if (chatFrameHost?.hasAttribute('collapsed')) {
lastAutoCollapsedWatchChatKey = watchPageKey;
return;
}
const hostToggleButton = chatFrameHost?.querySelector(
'#show-hide-button button, #show-hide-button yt-button-shape button, #show-hide-button tp-yt-paper-button'
);
if (hostToggleButton) {
hostToggleButton.click();
lastAutoCollapsedWatchChatKey = watchPageKey;
window.setTimeout(scheduleApply, 80);
return;
}
const chatFrame = watchFlexy.querySelector(
'ytd-live-chat-frame#chat #chatframe, ytd-live-chat-frame#chat iframe, #chatframe'
);
const chatDoc = safeGetIframeDocument(chatFrame);
if (!chatDoc) return;
const closeButton = Array.from(chatDoc.querySelectorAll('button')).find(
(button) => /close/i.test(button.getAttribute('aria-label') || '')
);
if (!closeButton) return;
closeButton.click();
lastAutoCollapsedWatchChatKey = watchPageKey;
window.setTimeout(scheduleApply, 80);
}
function syncWatchPlayerSize() {
if (!isWatchPage()) return;
const playerContainer = document.getElementById('player');
const moviePlayer = document.getElementById('movie_player');
if (!playerContainer || !moviePlayer) return;
const rect = playerContainer.getBoundingClientRect();
const width = Math.round(rect.width);
const height = Math.round(rect.height);
if (!width || !height) return;
const syncKey = `${location.href}|${width}x${height}`;
if (lastPlayerSyncKey === syncKey) return;
try {
if (typeof moviePlayer.setInternalSize === 'function') {
moviePlayer.setInternalSize(width, height);
}
if (typeof moviePlayer.setSize === 'function') {
moviePlayer.setSize(width, height);
}
} catch (error) {
console.debug('YouTube Layout Plus player sync failed:', error);
}
lastPlayerSyncKey = syncKey;
}
function schedulePlayerSync(delay = 0) {
if (playerSyncTimer) {
window.clearTimeout(playerSyncTimer);
playerSyncTimer = null;
}
playerSyncTimer = window.setTimeout(() => {
playerSyncTimer = null;
syncWatchPlayerSize();
}, delay);
}
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 normalizeHomeGridColumns(value) {
const columns = Number(value);
return HOME_GRID_COLUMN_OPTIONS.includes(columns) ? columns : 5;
}
function loadStoredHomeGridColumns() {
try {
return normalizeHomeGridColumns(window.localStorage.getItem(HOME_GRID_COLUMNS_STORAGE_KEY));
} catch {
return 5;
}
}
function persistThemeMode(mode) {
if (lastPersistedThemeMode === mode) return;
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);
}
lastPersistedThemeMode = mode;
} catch {
// Ignore storage failures.
}
}
function persistHomeGridColumns(columns) {
try {
window.localStorage.setItem(
HOME_GRID_COLUMNS_STORAGE_KEY,
String(normalizeHomeGridColumns(columns))
);
} catch {
// Ignore storage failures.
}
}
function applyHomeGridColumns() {
document.documentElement.style.setProperty(
'--tm-home-grid-items-per-row',
String(normalizeHomeGridColumns(currentHomeGridColumns))
);
}
function delay(ms) {
return new Promise((resolve) => {
window.setTimeout(resolve, ms);
});
}
function getThemeActionName(mode) {
return THEME_MODES.find((item) => item.mode === mode)?.actionName || 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=([0-9a-fA-F]+)/);
if (!f6Match) return null;
const f6 = parseInt(f6Match[1], 16);
if (!Number.isFinite(f6)) return null;
// Bit-decode. Light wins over dark (matches YouTube's reducer behavior
// where an explicit light bit overrides a residual dark bit).
if ((f6 & PREF_F6_LIGHT_BIT) !== 0) return 'light';
if ((f6 & PREF_F6_DARK_BIT) !== 0) return 'dark';
if ((f6 & PREF_F6_PREF_BIT) !== 0) return 'auto';
return null;
} catch {
return null;
}
}
function inferThemeModeFromDocument() {
return document.documentElement.hasAttribute('dark') ? 'dark' : 'light';
}
function getObservedThemeMode() {
return getThemeModeFromPrefCookie() || 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';
if (themeStatusHideTimer) window.clearTimeout(themeStatusHideTimer);
themeStatusHideTimer = window.setTimeout(() => {
status.dataset.visible = 'false';
themeStatusHideTimer = null;
}, 2600);
}
function hideThemeStatusMessage() {
const status = document.getElementById(THEME_STATUS_ID);
if (status) status.dataset.visible = 'false';
if (themeStatusHideTimer) {
window.clearTimeout(themeStatusHideTimer);
themeStatusHideTimer = null;
}
}
function isVisibleElement(element) {
if (!element) return false;
if (element.hidden) return false;
const style = window.getComputedStyle(element);
if (style.display === 'none' || style.visibility === 'hidden') return false;
const rect = element.getBoundingClientRect();
return rect.width > 0 && rect.height > 0;
}
function dispatchThemeSignal(mode) {
const actionName = getThemeActionName(mode);
if (!actionName) return false;
const target = document.querySelector('ytd-app') || document.documentElement;
try {
target.dispatchEvent(new CustomEvent('yt-action', {
detail: {
actionName,
args: [],
optionalAction: false,
returnValue: [],
},
bubbles: true,
cancelable: false,
composed: true,
}));
return true;
} catch (error) {
console.debug('YouTube Layout Plus theme dispatch failed:', error);
return false;
}
}
function updateThemeSwitcherUI({ force = false } = {}) {
const switcher = document.getElementById(THEME_SWITCHER_ID);
if (!switcher) return;
if (!pendingThemeMode) {
const observedMode = getObservedThemeMode();
if (observedMode && observedMode !== currentThemeMode) {
currentThemeMode = observedMode;
persistThemeMode(observedMode);
}
} else if (!currentThemeMode) {
currentThemeMode = loadStoredThemeMode() || inferThemeModeFromDocument();
}
const visibleMode = pendingThemeMode || currentThemeMode;
const disabled = themeSwitchInFlight;
if (!force
&& lastRenderedThemeMode === visibleMode
&& lastRenderedThemeDisabled === disabled) {
return;
}
lastRenderedThemeMode = visibleMode;
lastRenderedThemeDisabled = disabled;
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 = disabled;
}
}
async function verifyThemeMode(mode, { attempts = 10, intervalMs = 120 } = {}) {
// For Auto we can't rely on `<html dark>` alone because it reflects the
// current system preference, not the "Auto" mode itself. We only trust the
// cookie for Auto. For Light/Dark, cookie + attribute must both agree.
const expectedDarkAttr = mode === 'dark';
let lastResult = null;
for (let attempt = 0; attempt < attempts; attempt += 1) {
const cookieMode = getThemeModeFromPrefCookie();
const hasDark = document.documentElement.hasAttribute('dark');
const actualMode = cookieMode || inferThemeModeFromDocument();
const cookieConsistent = cookieMode ? cookieMode === mode : true;
const darkConsistent = mode === 'auto' ? true : hasDark === expectedDarkAttr;
lastResult = {
ok: cookieConsistent && darkConsistent,
actualMode,
cookieMode,
hasDark,
};
if (lastResult.ok) {
return lastResult;
}
await delay(intervalMs);
}
return lastResult || {
ok: false,
actualMode: getObservedThemeMode(),
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()
|| inferThemeModeFromDocument();
const expectedDarkAttr = mode === 'dark'
|| (mode === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches);
if (previousMode === mode
&& document.documentElement.hasAttribute('dark') === expectedDarkAttr) {
currentThemeMode = mode;
persistThemeMode(mode);
pendingThemeMode = null;
updateThemeSwitcherUI();
return;
}
themeSwitchInFlight = true;
pendingThemeMode = mode;
try {
updateThemeSwitcherUI();
if (!dispatchThemeSignal(mode)) {
currentThemeMode = previousMode;
persistThemeMode(previousMode);
showThemeStatusMessage(`Theme switch failed: unable to dispatch ${mode}.`);
return;
}
currentThemeMode = mode;
persistThemeMode(mode);
updateThemeSwitcherUI();
let verification = await verifyThemeMode(mode);
if (!verification.ok) {
// Silent single retry — cookie write can lag on the first dispatch.
dispatchThemeSignal(mode);
verification = await verifyThemeMode(mode, { attempts: 14, intervalMs: 140 });
}
if (!verification.ok) {
currentThemeMode = verification.actualMode || previousMode;
persistThemeMode(currentThemeMode);
showThemeStatusMessage(
`Theme switch failed: expected ${mode}, actual ${currentThemeMode || 'unknown'}.`
);
return;
}
currentThemeMode = verification.actualMode || mode;
persistThemeMode(currentThemeMode);
hideThemeStatusMessage();
} finally {
themeSwitchInFlight = false;
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);
let justCreated = false;
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);
justCreated = true;
} else if (switcher.previousElementSibling !== logoRenderer) {
logoRenderer.insertAdjacentElement('afterend', switcher);
}
updateThemeSwitcherUI({ force: justCreated });
}
function updateHomeGridSwitcherUI({ force = false } = {}) {
const switcher = document.getElementById(HOME_GRID_SWITCHER_ID);
if (!switcher) return;
const activeColumns = normalizeHomeGridColumns(currentHomeGridColumns);
if (!force && lastRenderedHomeGridColumns === activeColumns) return;
lastRenderedHomeGridColumns = activeColumns;
for (const button of switcher.querySelectorAll('.tm-home-grid-option[data-columns]')) {
const isActive = Number(button.dataset.columns) === activeColumns;
button.dataset.active = String(isActive);
button.setAttribute('aria-pressed', String(isActive));
}
}
function setHomeGridColumns(columns) {
const normalized = normalizeHomeGridColumns(columns);
if (normalized === currentHomeGridColumns) return;
currentHomeGridColumns = normalized;
persistHomeGridColumns(currentHomeGridColumns);
applyHomeGridColumns();
updateHomeGridSwitcherUI();
if (isHomePage()) {
scheduleApply();
// Nudge YouTube's grid renderer to re-measure — it only recomputes
// items-per-row on mount or resize events.
window.setTimeout(() => {
window.dispatchEvent(new Event('resize'));
}, 60);
}
}
function ensureHomeGridSwitcher() {
const anchor = document.getElementById(THEME_SWITCHER_ID)
|| document.querySelector('ytd-topbar-logo-renderer');
if (!anchor) return;
let switcher = document.getElementById(HOME_GRID_SWITCHER_ID);
let justCreated = false;
if (!switcher) {
switcher = document.createElement('div');
switcher.id = HOME_GRID_SWITCHER_ID;
switcher.setAttribute('role', 'group');
switcher.setAttribute('aria-label', 'Home grid columns');
for (const columns of HOME_GRID_COLUMN_OPTIONS) {
const optionButton = document.createElement('button');
optionButton.type = 'button';
optionButton.className = 'tm-home-grid-option';
optionButton.dataset.columns = String(columns);
optionButton.dataset.active = 'false';
optionButton.textContent = String(columns);
optionButton.setAttribute('aria-pressed', 'false');
optionButton.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
setHomeGridColumns(columns);
});
switcher.appendChild(optionButton);
}
anchor.insertAdjacentElement('afterend', switcher);
justCreated = true;
} else if (switcher.previousElementSibling !== anchor) {
anchor.insertAdjacentElement('afterend', switcher);
}
updateHomeGridSwitcherUI({ force: justCreated });
}
function applyLayout() {
ensureStyle();
applyHomeGridColumns();
try {
ensureThemeSwitcher();
ensureHomeGridSwitcher();
} catch (error) {
console.error('YouTube Layout Plus switcher insertion failed:', error);
}
if (isHomePage()) {
applyHomeLayout();
return;
}
if (isWatchPage()) {
applyWatchLayout();
return;
}
// Other YouTube routes: clear layout-specific classes so stray styles
// don't linger from the previous page.
setLayoutModeClasses([]);
}
function queueApply() {
if (applyFrame) return;
applyFrame = window.requestAnimationFrame(() => {
applyFrame = 0;
applyLayout();
});
}
function scheduleApply(delay = 0) {
if (delay > 0) {
window.clearTimeout(applyDelayTimer);
applyDelayTimer = window.setTimeout(() => {
applyDelayTimer = null;
queueApply();
}, delay);
return;
}
if (applyDelayTimer) {
window.clearTimeout(applyDelayTimer);
applyDelayTimer = null;
}
queueApply();
}
function stopWatchLayoutRetry() {
if (!watchLayoutRetryTimer) return;
window.clearTimeout(watchLayoutRetryTimer);
watchLayoutRetryTimer = null;
}
function startWatchLayoutRetry() {
stopWatchLayoutRetry();
if (!isWatchPage()) return;
watchLayoutRetryStep = 0;
const run = () => {
if (!isWatchPage()) {
stopWatchLayoutRetry();
return;
}
scheduleApply();
if (watchLayoutRetryStep >= WATCH_LAYOUT_RETRY_DELAYS.length - 1) {
stopWatchLayoutRetry();
return;
}
watchLayoutRetryStep += 1;
watchLayoutRetryTimer = window.setTimeout(run, WATCH_LAYOUT_RETRY_DELAYS[watchLayoutRetryStep]);
};
run();
}
function handleRouteChange(force = false) {
const nextUrl = location.href;
if (!force && nextUrl === currentUrl) return;
currentUrl = nextUrl;
// Reset inline-hidden replay prompts so a reused DOM node doesn't stay
// hidden on the next video.
clearHiddenReplayPrompts();
if (!isHomePage()) {
lastAutoCollapsedHomeUrl = '';
}
if (!isWatchPage()) {
lastAutoCollapsedWatchChatKey = '';
stopWatchLayoutRetry();
stopWatchChatStateSync();
lastWatchChatExpanded = null;
lastPlayerSyncKey = '';
if (playerSyncTimer) {
window.clearTimeout(playerSyncTimer);
playerSyncTimer = null;
}
} else {
lastWatchChatExpanded = null;
lastPlayerSyncKey = '';
startWatchLayoutRetry();
startWatchChatStateSync();
}
scheduleApply();
}
function startObservers() {
if (bodyObserver) return;
bodyObserver = new MutationObserver((mutations) => {
if (isWatchPage()) {
return;
}
const hasRelevantMutation = mutations.some((mutation) => {
const target = mutation.target instanceof Element
? mutation.target
: mutation.target?.parentElement;
if (!target) return false;
if (target.closest('#movie_player, .html5-video-player, #chatframe')) {
return false;
}
return true;
});
if (hasRelevantMutation) {
// Debounce to coalesce bursts of thumbnail / chip-bar mutations into
// a single apply instead of one per rAF.
scheduleApply(OBSERVER_APPLY_DEBOUNCE_MS);
}
});
bodyObserver.observe(document.body, {
childList: true,
subtree: true,
});
}
function startNavigationListeners() {
document.addEventListener('yt-navigate-finish', handleRouteChange, true);
window.addEventListener('popstate', handleRouteChange, true);
}
function startWindowListeners() {
window.addEventListener('resize', () => {
if (resizeTimer) {
window.clearTimeout(resizeTimer);
}
if (resizeFrame) {
window.cancelAnimationFrame(resizeFrame);
}
resizeFrame = window.requestAnimationFrame(() => {
resizeFrame = 0;
if (!isWatchPage()) return;
lastPlayerSyncKey = '';
syncWatchPlayerSize();
});
resizeTimer = window.setTimeout(() => {
resizeTimer = null;
if (!isWatchPage()) {
scheduleApply();
return;
}
lastPlayerSyncKey = '';
scheduleApply();
schedulePlayerSync(120);
}, 30);
}, true);
}
function startInteractionListeners() {
document.addEventListener('click', (event) => {
const trigger = event.target instanceof Element
? event.target.closest('button, yt-button-shape button, tp-yt-paper-button')
: null;
if (!trigger) return;
const text = (trigger.innerText || trigger.textContent || '').trim();
if (/open panel/i.test(text) && isWatchPage()) {
lastAutoCollapsedWatchChatKey = getWatchPageKey();
scheduleApply(80);
}
}, true);
}
function watchRoute() {
if (routeTimer) return;
routeTimer = window.setInterval(() => {
if (location.href === currentUrl) return;
handleRouteChange();
}, ROUTE_POLL_INTERVAL_MS);
}
function init() {
ensureStyle();
handleRouteChange(true);
startObservers();
startNavigationListeners();
startWindowListeners();
startInteractionListeners();
watchRoute();
}
init();
})();