YouTube Theater Mode Fix

Tries to revert some changes to the youtube theater / fullscreen mode to be like the old UI

// ==UserScript==
// @name         YouTube Theater Mode Fix
// @version      2.2
// @description  Tries to revert some changes to the youtube theater / fullscreen mode to be like the old UI
// @author       Torkelicious
// @icon         https://www.youtube.com/favicon.ico
// @license      GPL-3.0-or-later
// @match        https://www.youtube.com/*
// @run-at       document-start
// @namespace    https://greasyfork.org/users/1403155
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// ==/UserScript==

(function () {
  'use strict';

  // avoid loading more than once !!!
  if (window.ytTheaterFixLoaded) return;
  window.ytTheaterFixLoaded = true;

  const DEFAULTS = { mastheadBehavior: 'relative' };
  const COSMETIC = [
    '.ytp-fullscreen-grid-hover-overlay',
    '.ytp-fullscreen-grid-hover-overlay-chevron',
    '.ytp-fullscreen-grid-stills-container'
  ];
  const STYLE_ID = 'yt-theater-fix-styles';

  const STYLE_TEXT = `
    ytd-app { overflow: auto !important; }
    ytd-app[scrolling] {
      position: absolute !important;
      top: 0 !important; left: 0 !important;
      right: calc((var(--ytd-app-fullerscreen-scrollbar-width) + 1px)*-1) !important;
      bottom: 0 !important;
      overflow-x: auto !important;
    }
    ytd-watch-flexy[full-bleed-player] #single-column-container.ytd-watch-flexy,
    ytd-watch-flexy[full-bleed-player] #columns.ytd-watch-flexy {
      display: flex !important;
    }

    /* Keep controls visible and hide the new fullscreen grid UI variants */
    .ytp-fullscreen-grid-peeking.ytp-full-bleed-player:not(.ytp-autohide) .ytp-chrome-bottom {
      bottom: 0 !important; opacity: 1 !important;
    }
    #movie_player:not(.ytp-grid-ended-state) .ytp-fullscreen-grid {
      display: none !important; top: 100% !important; opacity: 0 !important;
    }
    ${COSMETIC.map(sel => `${sel} { display: none !important; }`).join('\n')}

    .html5-video-player.ytp-fullscreen:is(.ytp-fullscreen-grid-peeking,.ytp-fullscreen-grid-active) .ytp-fullscreen-grid {
      display: none !important;
    }
    .html5-video-player.ytp-fullscreen:is(.ytp-fullscreen-grid-peeking,.ytp-fullscreen-grid-active) .ytp-gradient-bottom {
      display: none !important;
    }
    /* Generic fallbacks for renamed classes */
    .html5-video-player.ytp-fullscreen [class*="fullscreen-grid"] { display: none !important; }
    .html5-video-player.ytp-fullscreen [class*="gradient-bottom"] { display: none !important; }

    .html5-video-player.ytp-fullscreen:is(.ytp-fullscreen-grid-peeking,.ytp-fullscreen-grid-active) .ytp-chrome-bottom {
      bottom: 0px !important;
    }
    .html5-video-player.ytp-fullscreen:is(.ytp-fullscreen-grid-peeking,.ytp-fullscreen-grid-active):not(:is(.ytp-autohide,.ytp-autohide-active)) .ytp-chrome-bottom {
      opacity: unset !important;
    }
    .html5-video-player.ytp-fullscreen:is(.ytp-fullscreen-grid-peeking,.ytp-fullscreen-grid-active) .ytp-overlays-container {
      bottom: calc(var(--yt-delhi-bottom-controls-height, 72px) + 30px) !important;
    }
    .html5-video-player.ytp-fullscreen:is(.ytp-fullscreen-grid-peeking,.ytp-fullscreen-grid-active):not(:is(.ytp-autohide,.ytp-autohide-active)) .ytp-overlays-container {
      opacity: unset !important;
    }
    div.html5-video-player {
      --ytp-grid-scroll-percentage: 0 !important;
    }
    div.html5-video-player.ytp-fullscreen-grid-active .ytp-chrome-bottom {
      display: block !important;
    }
  `;

  // defensive GM wrappers (in case manager doesn't expose GM_* exactly)
  const hasGMGet = typeof GM_getValue === 'function';
  const hasGMSet = typeof GM_setValue === 'function';
  const gmGet = (k) => (hasGMGet ? GM_getValue(k) : undefined) ?? DEFAULTS[k];
  const gmSet = (k, v) => { if (hasGMSet) GM_setValue(k, v); };

  const setImp = (el, prop, val) => el?.style?.setProperty(prop, val, 'important');
  const removeImp = (el, prop) => el?.style?.removeProperty(prop);

  function injectStylesEarly() {
    if (document.getElementById(STYLE_ID)) return;
    const st = document.createElement('style');
    st.id = STYLE_ID;
    st.textContent = STYLE_TEXT;
    (document.head || document.documentElement).appendChild(st);
  }

  function applyWatchFixes() {
    const watch = document.querySelector('ytd-watch-flexy');
    const masthead = document.querySelector('#masthead-container');
    const app = document.querySelector('ytd-app');
    const pm = document.getElementById('page-manager');
    if (!watch || !app) return;

    const player = document.getElementById('movie_player') || document.querySelector('.html5-video-player');
    const behavior = gmGet('mastheadBehavior');

    // mode detection
    const isTheater = watch.hasAttribute('theater');
    const isFullscreen =
      watch.hasAttribute('fullscreen') ||
      !!document.fullscreenElement ||
      player?.classList.contains('ytp-fullscreen');

    const hasMini = document.querySelector('ytd-miniplayer')?.hasAttribute('active');

    if (isTheater || isFullscreen) {
      if (!hasMini && masthead) {
        if (isFullscreen || behavior === 'hide') {
          setImp(masthead, 'display', 'none');
        } else if (behavior === 'relative') {
          removeImp(masthead, 'display');
          setImp(masthead, 'position', 'relative');
          setImp(masthead, 'z-index', '2020');
        } else {
          removeImp(masthead, 'display');
          removeImp(masthead, 'position');
          removeImp(masthead, 'z-index');
        }
      }

      if (!hasMini) {
        setImp(app, 'overflow', 'auto');
        if (app.hasAttribute('scrolling')) {
          setImp(app, 'position', 'absolute');
          setImp(app, 'top', '0');
          setImp(app, 'left', '0');
          setImp(app, 'right', 'calc((var(--ytd-app-fullerscreen-scrollbar-width) + 1px)*-1)');
          setImp(app, 'bottom', '0');
          setImp(app, 'overflow-x', 'auto');
        }
      }

      const sc =
        document.querySelector('#single-column-container.ytd-watch-flexy') ||
        document.getElementById('single-column-container');
      const cols =
        document.querySelector('#columns.ytd-watch-flexy') ||
        document.getElementById('columns');
      if (sc) setImp(sc, 'display', 'flex');
      if (cols) setImp(cols, 'display', 'flex');

      if (behavior === 'relative' && !isFullscreen && pm) setImp(pm, 'margin-top', '0');
    } else {
      if (masthead) {
        removeImp(masthead, 'display');
        removeImp(masthead, 'position');
        removeImp(masthead, 'z-index');
      }
      if (pm) removeImp(pm, 'margin-top');
    }
  }

  let debounceTimer = null;
  function scheduleRun(delay = 100) {
    clearTimeout(debounceTimer);
    debounceTimer = setTimeout(() => {
      applyWatchFixes();
      // Retry once if watch page not loaded yet
      if (!document.querySelector('ytd-watch-flexy') && location.pathname.includes('/watch')) {
        setTimeout(applyWatchFixes, 500);
      }
    }, delay);
  }

  const observer = new MutationObserver((mutations) => {
    for (const m of mutations) {
      if (
        (m.type === 'childList' && [...m.addedNodes].some(n => n.matches?.('ytd-watch-flexy, ytd-miniplayer, #movie_player, .html5-video-player'))) ||
        (m.type === 'attributes' && (
          m.target.matches?.('ytd-watch-flexy, ytd-miniplayer') ||
          m.target.id === 'movie_player' ||
          m.target.matches?.('.html5-video-player')
        ))
      ) {
        scheduleRun(50);
        break;
      }
    }
  });

  function startObserver() {
    const target = document.querySelector('ytd-app') || document.documentElement;
    observer.observe(target, {
      childList: true,
      subtree: true,
      attributes: true,
      attributeFilter: ['theater', 'fullscreen', 'active', 'class']
    });
  }

  //  settings dialog using DOM methods to avoid TrustedTypes TrustedHTML errors
  function showSettings() {
    if (document.getElementById('yt-theater-fix-settings')) return;
    const current = gmGet('mastheadBehavior');

    const overlay = document.createElement('div');
    overlay.id = 'yt-theater-fix-settings';
    overlay.style.cssText = 'position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:99999;background-color:rgba(0,0,0,0.6)';

    const dialog = document.createElement('div');
    dialog.style.cssText = 'background:#222;color:#fff;padding:18px;border-radius:8px;min-width:320px;font-family:Arial,Helvetica,sans-serif;box-shadow:0 4px 12px rgba(0,0,0,0.5);';

    const title = document.createElement('h3');
    title.textContent = 'Theater Mode Settings';
    title.style.cssText = 'margin:0 0 12px 0;font-size:16px';

    const options = document.createElement('div');
    options.style.cssText = 'display:flex;flex-direction:column;gap:8px';

    function mkOption(val, text) {
      const label = document.createElement('label');
      const input = document.createElement('input');
      input.type = 'radio';
      input.name = 'mf';
      input.value = val;
      if (current === val) input.checked = true;
      label.appendChild(input);
      label.appendChild(document.createTextNode(' ' + text));
      return label;
    }

    options.appendChild(mkOption('relative', 'Show masthead (scrollable)'));
    options.appendChild(mkOption('hide', 'Hide masthead'));
    options.appendChild(mkOption('fixed', 'Show masthead (fixed)'));

    const controls = document.createElement('div');
    controls.style.cssText = 'display:flex;gap:8px;justify-content:flex-end;margin-top:12px';

    const btnCancel = document.createElement('button');
    btnCancel.id = 'yt-m-cancel';
    btnCancel.textContent = 'Cancel';

    const btnSave = document.createElement('button');
    btnSave.id = 'yt-m-save';
    btnSave.textContent = 'Save & Reload';

    controls.appendChild(btnCancel);
    controls.appendChild(btnSave);

    dialog.appendChild(title);
    dialog.appendChild(options);
    dialog.appendChild(controls);
    overlay.appendChild(dialog);

    (document.documentElement || document.body || document).appendChild(overlay);

    const close = () => overlay.remove();
    overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
    btnCancel.onclick = close;
    btnSave.onclick = () => {
      const v = document.querySelector('input[name="mf"]:checked')?.value || DEFAULTS.mastheadBehavior;
      gmSet('mastheadBehavior', v);
      close();
      location.reload();
    };
  }

  // register menu command fail silently
  try {
    if (typeof GM_registerMenuCommand === 'function') GM_registerMenuCommand('Theater Mode Settings', showSettings);
  } catch (e) { /* ignore */ }

  // Re run on SPA navigations and fullscreen toggles
  window.addEventListener('yt-navigate-finish', () => scheduleRun(50), { passive: true });
  document.addEventListener('fullscreenchange', () => scheduleRun(20), { passive: true });

  // Init
  injectStylesEarly();
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', () => {
      scheduleRun(50);
      startObserver();
    });
  } else {
    scheduleRun(50);
    startObserver();
  }
})();