// ==UserScript== // @name YouTube Layout Plus // @namespace https://qazwsx123.uk/ // @version 0.2.6 // @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. // @author Codex // @match https://www.youtube.com/* // @run-at document-idle // @grant none // @downloadURL 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 applyFrame = 0; let applyDelayTimer = null; let bodyObserver = null; let routeTimer = null; 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 themeSwitchInFlight = false; let pendingThemeMode = null; 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; } #${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-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; } 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; } 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 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'); if (hasWatchSidebarChat(watchFlexy)) { document.documentElement.classList.add('tm-youtube-layout-plus-watch-chat'); updateWatchChatPanelState(watchFlexy); autoCollapseLiveChatPanel(watchFlexy); syncWatchPlayerSize(); schedulePlayerSync(120); return; } document.documentElement.classList.add('tm-youtube-layout-plus-watch-plain'); hideWatchReplayPrompt(); syncWatchPlayerSize(); schedulePlayerSync(120); } function hideWatchReplayPrompt() { const teaserCarousel = document.querySelector('ytd-watch-metadata #teaser-carousel'); if (!teaserCarousel) return; const teaserText = getWatchTeaserText(); if (hasLiveWatchPrompt(teaserText)) { return; } const watchFlexy = document.querySelector('ytd-watch-flexy'); if (watchFlexy && hasReplayWatchPrompt(teaserText) && hasWatchSidebarChat(watchFlexy)) { 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) return; teaserCarousel.style.display = 'none'; } 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; // Let the class change apply first, then resync the player size so // collapsing/expanding chat updates the video dimensions immediately. 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 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 = chatFrame?.contentDocument; 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); } if (typeof moviePlayer.setSizeStyle === 'function') { moviePlayer.setSizeStyle(width, height); } } catch (error) { console.debug('YouTube Layout Plus player sync failed:', error); } window.dispatchEvent(new Event('resize')); lastPlayerSyncKey = syncKey; } function schedulePlayerSync(delay = 0) { if (playerSyncTimer) { window.clearTimeout(playerSyncTimer); playerSyncTimer = null; } playerSyncTimer = window.setTimeout(() => { playerSyncTimer = null; syncWatchPlayerSize(); }, delay); } function clearModeClasses() { document.documentElement.classList.remove( 'tm-youtube-layout-plus-home', 'tm-youtube-layout-plus-watch', 'tm-youtube-layout-plus-watch-chat', 'tm-youtube-layout-plus-watch-chat-open', 'tm-youtube-layout-plus-watch-chat-collapsed', 'tm-youtube-layout-plus-watch-plain' ); } 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 getThemeModeFromOptionText(text) { const normalizedText = (text || '').trim(); if (!normalizedText) return null; if (/use device theme|device theme/i.test(normalizedText)) return 'auto'; if (/light theme/i.test(normalizedText)) return 'light'; if (/dark theme/i.test(normalizedText)) return 'dark'; return 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; 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 getOfficialThemeOptions(options = {}) { const { visibleOnly = false } = options; return Array.from(document.querySelectorAll('ytd-compact-link-renderer')).filter( (option) => { const optionSignal = getThemeOptionSignal(option); const optionMode = getThemeModeFromOptionText(option.innerText || option.textContent); return ( (THEME_MODES.some((mode) => optionSignal === mode.signal) || !!optionMode) && (!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 || getThemeModeFromOptionText(selectedOption.innerText || selectedOption.textContent); } 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, a, button, tp-yt-paper-item') ).find((entry) => { const text = (entry.innerText || entry.textContent || '').trim(); return /^appearance:/i.test(text) && isVisibleElement(entry); }) || null; } function clickMenuEntry(entry) { const target = entry?.querySelector?.('a#endpoint, button, tp-yt-paper-item') || entry?.closest?.('a, button') || entry; target?.click?.(); } 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) { clickMenuEntry(existingAppearanceEntry); 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; clickMenuEntry(appearanceEntry); 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 < 12; attempt += 1) { const cookieMode = getThemeModeFromPrefCookie(); const hasDark = document.documentElement.hasAttribute('dark'); const actualMode = cookieMode || inferThemeModeFromDocument(); const cookieConsistent = cookieMode === mode; const darkConsistent = hasDark === expectedDark; lastResult = { ok: cookieConsistent && darkConsistent, actualMode, cookieMode, hasDark, }; if (lastResult.ok) { return lastResult; } await delay(120); } 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() || 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; } clickMenuEntry(targetOption); 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 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; 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; // Ignore the player's internal UI churn elsewhere. Those mutations are // high-frequency and do not require reapplying layout classes. if (target.closest('#movie_player, .html5-video-player, #chatframe')) { return false; } return true; }); if (hasRelevantMutation) { scheduleApply(); } }); bodyObserver.observe(document.body, { childList: true, subtree: true, }); } function startNavigationListeners() { document.addEventListener('yt-navigate-finish', handleRouteChange, true); window.addEventListener('popstate', handleRouteChange, 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(); }, 1000); } function init() { ensureStyle(); handleRouteChange(true); startObservers(); startNavigationListeners(); startInteractionListeners(); watchRoute(); } init(); })();