YouTube - Miniplayer

Shows a mini player when you scroll past the video.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

// ==UserScript==
// @name                    YouTube - Miniplayer
// @name:fr                 YouTube - Lecteur réduit
// @name:es                 YouTube - Minirreproductor
// @name:de                 YouTube - Miniplayer
// @name:it                 YouTube - Miniplayer
// @name:zh-CN              YouTube - Miniplayer
// @namespace               https://gist.github.com/4lrick/5a54e121bdc9056a7551529669d65ae6
// @version                 1.1
// @description             Shows a mini player when you scroll past the video.
// @description:fr          Affiche un lecteur réduit quand vous faites défiler sous la vidéo.
// @description:es          Muestra un minirreproductor al desplazarte más allá del video.
// @description:de          Zeigt einen Miniplayer, sobald Sie am Video vorbeiscrollen.
// @description:it          Mostra un miniplayer quando scorri oltre il video.
// @description:zh-CN       在滚动越过视频时显示迷你播放器。
// @author                  4lrick
// @match                   https://www.youtube.com/*
// @icon                    https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @grant                   none
// @license                 GPL-3.0-only
// ==/UserScript==

(function () {
    'use strict';

    const CFG = {
        rawVideoSelector: '.html5-video-container',
        mainPlayerSelector: '#movie_player',
        miniPlayerClass: 'mpu-mini-player',
        widthPx: 480,
        scrollThreshold: 300,
        autohideDelayMs: 2000,
        transitionDurationMs: 300,
    };

    function injectStyle() {
        if (document.getElementById('mpu-style')) return;
        const style = document.createElement('style');
        style.id = 'mpu-style';
        style.textContent = `
        /* Mini player container */
        .${CFG.miniPlayerClass} {
            position: fixed !important;
            width: ${CFG.widthPx}px !important;
            height: ${Math.round(CFG.widthPx * 9 / 16)}px !important;
            right: 16px !important;
            bottom: 16px !important;
            background: #000;
            overflow: hidden !important;
            border-radius: 12px !important;
            opacity: 0;
            transform: scale(0.8) !important;
            transition: opacity ${CFG.transitionDurationMs}ms ease, transform ${CFG.transitionDurationMs}ms ease !important;
            user-select: none;
        }
        .${CFG.miniPlayerClass}.visible {
            opacity: 1;
            transform: scale(1) !important;
        }

        /* Ensure the video fills the mini player */
        .${CFG.miniPlayerClass} ${CFG.rawVideoSelector},
        .${CFG.miniPlayerClass} video.html5-main-video {
            width: 100% !important;
            height: 100% !important;
            left: 0 !important;
        }
        .${CFG.miniPlayerClass} ${CFG.rawVideoSelector} {
            position: static !important;
        }

        /* Controls bar */
        .${CFG.miniPlayerClass} .mpu-controls {
            position: absolute !important;
            left: 0; right: 0; bottom: 0;
            display: flex; align-items: center; gap: 8px;
            color: #fff; font: 12px/1.2 system-ui, sans-serif;
            user-select: none; pointer-events: auto;
            padding: 4px;
        }

        /* Bottom buttons */
        .${CFG.miniPlayerClass} .mpu-btn {
            cursor: pointer;
        }
        .${CFG.miniPlayerClass} .mpu-btn-play,
        .${CFG.miniPlayerClass} .mpu-btn-fullscreen {
            background: transparent; border: none;
            display: flex;
        }
        .${CFG.miniPlayerClass} .mpu-btn-play::before {
            content: '';
            width: 28px; height: 28px;
            background-image: url('data:image/svg+xml;utf8,<svg viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg"><path d="M12,26 L18.5,22 L18.5,14 L12,10 Z M18.5,22 L25,18 L25,18 L18.5,14 Z" fill="%23fff"/></svg>');
            background-size: contain;
            background-repeat: no-repeat;
        }
        .${CFG.miniPlayerClass} .mpu-btn-play.playing::before {
            background-image: url('data:image/svg+xml;utf8,<svg viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg"><path d="M12,26 L16,26 L16,10 L12,10 Z M21,26 L25,26 L25,10 L21,10 Z" fill="%23fff"/></svg>');
        }
        .${CFG.miniPlayerClass} .mpu-btn-fullscreen { margin-left: auto; }
        .${CFG.miniPlayerClass} .mpu-btn-fullscreen::before {
            content: '';
            width: 28px; height: 28px;
            background-image: url('data:image/svg+xml;utf8,<svg viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg"><path d="M10,16 h2 v-4 h4 v-2 h-6 v6 z" fill="%23fff"/><path d="M20,10 v2 h4 v4 h2 v-6 h-6 z" fill="%23fff"/><path d="M24,24 h-4 v2 h6 v-6 h-2 v4 z" fill="%23fff"/><path d="M12,20 h-2 v6 h6 v-2 h-4 v-4 z" fill="%23fff"/></svg>');
            background-size: contain;
            background-repeat: no-repeat;
        }
        .${CFG.miniPlayerClass} .mpu-btn-fullscreen.fullscreen::before {
            background-image: url('data:image/svg+xml;utf8,<svg viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg"><path d="M14,14 h-4 v2 h6 v-6 h-2 v4 z" fill="%23fff"/><path d="M22,14 v-4 h-2 v6 h6 v-2 h-4 z" fill="%23fff"/><path d="M20,26 h2 v-4 h4 v-2 h-6 v6 z" fill="%23fff"/><path d="M10,22 h4 v4 h2 v-6 h-6 v2 z" fill="%23fff"/></svg>');
        }

        /* Top buttons */
        .${CFG.miniPlayerClass} .mpu-btn-top {
            position: absolute; top: 0 !important;
            background: transparent; border: none;
            display: flex;
            padding: 8px;
        }
        .${CFG.miniPlayerClass} .mpu-btn-close { right: 0; }
        .${CFG.miniPlayerClass} .mpu-btn-scroll-up::before {
            content: '';
            width: 24px; height: 24px;
            background-image: url('data:image/svg+xml;utf8,<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g transform="translate(12,12) scale(-1,1) translate(-12,-12)"><path d="M19,19 L5,19 L5,5 L12,5 L12,3 L5,3 C3.89,3 3,3.9 3,5 L3,19 C3,20.1 3.89,21 5,21 L19,21 C20.1,21 21,20.1 21,19 L21,12 L19,12 L19,19 Z M14,3 L14,5 L17.59,5 L7.76,14.83 L9.17,16.24 L19,6.41 L19,10 L21,10 L21,3 L14,3 Z" fill="%23fff"/></g></svg>');
            background-size: contain;
            background-repeat: no-repeat;
        }
        .${CFG.miniPlayerClass} .mpu-btn-close::before {
            content: '';
            width: 24px; height: 24px;
            background-image: url('data:image/svg+xml;utf8,<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" fill="%23fff"/></svg>');
            background-size: contain;
            background-repeat: no-repeat;
        }

        /* Autohide behavior */
        .${CFG.miniPlayerClass} .mpu-controls,
        .${CFG.miniPlayerClass} .mpu-btn-top,
        .${CFG.miniPlayerClass} .mpu-progress-container,
        .${CFG.miniPlayerClass} .ytp-gradient { transition: opacity 150ms ease; }

        .${CFG.miniPlayerClass}.ytp-autohide .mpu-controls,
        .${CFG.miniPlayerClass}.ytp-autohide .mpu-btn-top,
        .${CFG.miniPlayerClass}.ytp-autohide .mpu-progress-container,
        .${CFG.miniPlayerClass}.ytp-autohide .ytp-gradient {
            opacity: 0; pointer-events: none;
        }

        /* Progress bar */
        .${CFG.miniPlayerClass} .mpu-progress-container {
        position: absolute; left: 8px; right: 8px; bottom: 40px;
        }
        @-moz-document url-prefix() {
            .${CFG.miniPlayerClass} .mpu-progress-container {
            bottom: 30px;
            }
        }

        .${CFG.miniPlayerClass} .mpu-slider {
            --bar-h: 4px;
            --thumb: 13px;
            --fill: 0%;

            --accent: var(--yt-spec-static-brand-red, #f03);
            --accent2: #ff2791;
            --track-bg: rgba(255,255,255,0.2);
            --fill-gradient: linear-gradient(to right, var(--accent) 80%, var(--accent2) 100%);

            -webkit-appearance: none;
            width: -webkit-fill-available;
            width: -moz-available;
            cursor: pointer;
            background: none;
        }
        .${CFG.miniPlayerClass} .mpu-slider:hover { --bar-h: 6px; }

        .${CFG.miniPlayerClass} .mpu-slider::-webkit-slider-runnable-track {
            height: var(--bar-h);
            background:
            var(--fill-gradient) 0 50% / var(--fill) var(--bar-h) no-repeat,
            linear-gradient(var(--track-bg), var(--track-bg)) 0 50% / 100% var(--bar-h) no-repeat;
        }
        .${CFG.miniPlayerClass} .mpu-slider::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: var(--thumb); height: var(--thumb);
            border-radius: 50%;
            background: var(--accent);
            margin-top: calc((var(--bar-h) - var(--thumb)) / 2);
        }

        .${CFG.miniPlayerClass} .mpu-slider::-moz-range-track {
            height: var(--bar-h);
            background: var(--track-bg);
        }
        .${CFG.miniPlayerClass} .mpu-slider::-moz-range-progress {
            height: var(--bar-h);
            background: var(--fill-gradient);
        }
        .${CFG.miniPlayerClass} .mpu-slider::-moz-range-thumb {
            width: var(--thumb); height: var(--thumb);
            border: 0; border-radius: 50%;
            background: var(--accent);
        }

        /* Gradient behind controls */
        .${CFG.miniPlayerClass} .ytp-gradient {
            position: absolute; left: 0; right: 0;
            height: 64px; pointer-events: none;
            background: linear-gradient(to top, rgba(0,0,0,0.9), rgba(0,0,0,0));
        }

    `;
        document.head.appendChild(style);
    }

    let rawVideo;
    let miniPlayer;
    let playerInitialLocation = null;
    let controls = null;
    let isPlayerHidden = false;
    let autohideTimer = null;
    let observer = null;
    let loopStarted = false;
    let isDragging = false;
    let dragStartX = 0;
    let dragStartY = 0;
    let dragStartLeft = 0;
    let dragStartTop = 0;

    function restoreFromMiniPlayer() {
        if (!rawVideo) return;
        if (!playerInitialLocation) return;
        const parent = playerInitialLocation.parentNode;
        if (!parent) return;
        parent.insertBefore(rawVideo, playerInitialLocation);
        if (miniPlayer) {
            miniPlayer.classList.remove('visible');
            setTimeout(() => {
                miniPlayer.remove();
                miniPlayer = null;
                controls = null;
            }, CFG.transitionDurationMs);
        }
        playerInitialLocation = null;
    }

    function attachAutohideListeners() {
        miniPlayer.addEventListener('mousemove', showControlsNow);
        miniPlayer.addEventListener('mouseenter', showControlsNow);
        miniPlayer.addEventListener('touchstart', showControlsNow, { passive: true });
        miniPlayer.addEventListener('focusin', showControlsNow);
        miniPlayer.addEventListener('mouseleave', scheduleAutohide);
    }

    function attachFullscreenListener() {
        if (!controls) return;
        const { fullscreenBtn } = controls;
        function onFullscreenChange() {
            if (document.fullscreenElement) {
                fullscreenBtn.classList.add('fullscreen');
                fullscreenBtn.title = 'Exit full screen';
            } else {
                fullscreenBtn.classList.remove('fullscreen');
                fullscreenBtn.title = 'Full screen';
            }
        }
        document.addEventListener('fullscreenchange', onFullscreenChange);
    }

    function updatePlayIcon() {
        if (!controls) return;
        const video = getVideo();
        const isPlaying = video && !video.paused;
        if (isPlaying) {
            controls.playBtn.classList.add('playing');
            controls.playBtn.title = 'Pause';
        } else {
            controls.playBtn.classList.remove('playing');
            controls.playBtn.title = 'Play';
        }
    }

    function attachVideoListeners() {
        const video = getVideo();
        if (!video) return;
        video.addEventListener('play', updatePlayIcon);
        video.addEventListener('pause', updatePlayIcon);
        video.addEventListener('timeupdate', () => { updateBars(); updateTime(); });
        updatePlayIcon();
        updateBars();
        updateTime();
    }

    function formatTime(t) {
        if (!isFinite(t) || t < 0) return '--:--';
        const s = Math.floor(t % 60).toString().padStart(2, '0');
        const m = Math.floor((t / 60) % 60).toString();
        const h = Math.floor(t / 3600);
        return h > 0 ? `${h}:${m.padStart(2, '0')}:${s}` : `${m}:${s}`;
    }

    function updateTime() {
        if (!controls) return;
        const { time } = controls;
        const video = getVideo();
        if (video) {
            time.textContent = `${formatTime(video.currentTime)} / ${formatTime(video.duration)}`;
        } else {
            time.textContent = '--:-- / --:--';
        }
    }

    function isLive() {
        const mainPlayer = document.querySelector(CFG.mainPlayerSelector);
        if (!mainPlayer) return false;
        const timeDisplay = mainPlayer.querySelector('.ytp-time-display');
        return !!(timeDisplay && timeDisplay.classList.contains('ytp-live'));
    }

    function updateBars() {
        if (!controls) return;
        const { progressBar } = controls;
        const video = getVideo();
        if (!video) return;
        let progressFraction = Math.max(0, Math.min(1, video.currentTime / video.duration));
        progressBar.slider.value = progressFraction;
        progressBar.slider.style.setProperty('--fill', `${progressFraction * 100}%`);
    }

    function attachControlListeners() {
        if (!controls) return;
        const { playBtn, fullscreenBtn, scrollUpBtn, closeBtn, progressBar } = controls;

        function onPlayClick() {
            const video = getVideo();
            if (!video) return;
            if (video.paused) video.play(); else video.pause();
            showControlsNow();
        }

        function onFullscreenClick() {
            if (document.fullscreenElement) document.exitFullscreen();
            else displayMiniPlayer().requestFullscreen();
            showControlsNow();
        }

        function onUpClick() {
            window.scrollTo({ top: 0, behavior: 'smooth' });
            showControlsNow();
        }

        function onCloseClick() {
            isPlayerHidden = true;
            toggleMiniPlayer(false);
        }

        function onProgressInput(event) {
            const newProgressFraction = parseFloat(event.target.value);
            const video = getVideo();
            if (!video) return;
            video.currentTime = newProgressFraction * video.duration;
            updateBars();
            updateTime();
            showControlsNow();
        }

        playBtn.addEventListener('click', onPlayClick);
        fullscreenBtn.addEventListener('click', onFullscreenClick);
        scrollUpBtn.addEventListener('click', onUpClick);
        closeBtn.addEventListener('click', onCloseClick);
        progressBar.slider.addEventListener('input', onProgressInput);
    }

    function createGradient(position = 'bottom') {
        const gradient = document.createElement('div');
        gradient.className = 'ytp-gradient';
        if (position === 'top') {
            gradient.style.top = '-20px';
            gradient.style.transform = 'rotate(180deg)';
        } else {
            gradient.style.bottom = '-5px';
        }
        return gradient;
    }

    function createProgressBar() {
        const progressContainer = document.createElement('div');
        progressContainer.className = 'mpu-progress-container';

        const slider = document.createElement('input');
        slider.className = 'mpu-slider';
        slider.type = 'range';
        slider.min = '0';
        slider.max = '1';
        slider.step = '0.001';
        slider.value = '0';

        progressContainer.appendChild(slider);

        return {
            progressContainer,
            slider
        };
    }

    function createButton(type, text) {
        const btn = document.createElement('button');
        let className = `mpu-btn mpu-btn-${type}`;

        if (type === 'scroll-up' || type === 'close') {
            className += ' mpu-btn-top';
        }

        btn.className = className;
        btn.title = text;
        return btn;
    }

    function displayControls() {
        if (controls && controls.root && miniPlayer && miniPlayer.contains(controls.root)) return controls;

        const mainPlayer = document.querySelector(CFG.mainPlayerSelector);
        if (!mainPlayer) return null;

        const bottomGradient = createGradient('bottom');
        const topGradient = createGradient('top');
        miniPlayer.appendChild(bottomGradient);
        miniPlayer.appendChild(topGradient);

        const controlsRoot = document.createElement('div');
        controlsRoot.className = 'mpu-controls';

        const playBtn = createButton('play', 'Play/Pause');
        const fullscreenBtn = createButton('fullscreen', 'Full screen');
        const scrollUpBtn = createButton('scroll-up', 'Scroll to top');
        const closeBtn = createButton('close', 'Close');

        const time = document.createElement('span');
        time.textContent = '--:-- / --:--';

        controlsRoot.append(playBtn, time, fullscreenBtn);
        miniPlayer.appendChild(controlsRoot);

        const progressBar = createProgressBar();
        miniPlayer.appendChild(progressBar.progressContainer);

        miniPlayer.appendChild(scrollUpBtn);
        miniPlayer.appendChild(closeBtn);
        controls = { root: controlsRoot, playBtn, fullscreenBtn, time, scrollUpBtn, closeBtn, progressBar, bottomGradient, topGradient };
        return controls;
    }

    function onTimestampClickPreserveScroll(event) {
        if (isPlayerHidden) return;
        const anchor = event.target?.closest('a[href]');
        if (!anchor?.closest('#comments, ytd-comments')) return;

        const text = anchor.textContent?.trim();
        if (!text) return;

        const parts = text.split(':').map(p => parseInt(p, 10));
        if (parts.some(n => isNaN(n))) return;

        let seconds;
        if (parts.length === 2) {
            seconds = parts[0] * 60 + parts[1];
        } else if (parts.length === 3) {
            seconds = parts[0] * 3600 + parts[1] * 60 + parts[2];
        } else {
            return;
        }

        const videoElement = rawVideo?.querySelector('video');
        if (!videoElement?.duration) return;

        event.preventDefault();
        event.stopPropagation();

        videoElement.currentTime = Math.max(0, Math.min(videoElement.duration, seconds));
        showControlsNow();
    }

    function clearAutohideTimer() {
        if (autohideTimer) {
            clearTimeout(autohideTimer); autohideTimer = null;
        }
    }

    function scheduleAutohide() {
        clearAutohideTimer();
        autohideTimer = setTimeout(() => {
            if (miniPlayer) miniPlayer.classList.add('ytp-autohide');
        }, CFG.autohideDelayMs);
    }

    function showControlsNow() {
        miniPlayer.classList.remove('ytp-autohide');
        scheduleAutohide();
    }

    function getVideo() {
        return rawVideo ? rawVideo.querySelector('video') : null;
    }

    function onMiniPlayerBackgroundClick(event) {
        if (event.button !== 0) return;

        const mouseMovement = Math.abs(event.clientX - dragStartX) + Math.abs(event.clientY - dragStartY);
        if (mouseMovement > 1) return;

        const target = event.target;
        if (!target) return;
        if (isTargetInteractive(target)) return;
        const videoElement = getVideo();
        if (!videoElement) return;
        if (videoElement.paused) videoElement.play(); else videoElement.pause();
        showControlsNow();
    }

    function handleDragEnd() {
        isDragging = false;
        document.removeEventListener('mousemove', handleDragMove);
        document.removeEventListener('mouseup', handleDragEnd);
    }

    function handleDragMove(event) {
        if (!isDragging) return;

        const deltaX = event.clientX - dragStartX;
        const deltaY = event.clientY - dragStartY;

        const newLeft = Math.max(0, Math.min(window.innerWidth - CFG.widthPx, dragStartLeft + deltaX));
        const newTop = Math.max(0, Math.min(window.innerHeight - Math.round(CFG.widthPx * 9 / 16), dragStartTop + deltaY));

        miniPlayer.style.left = newLeft + 'px';
        miniPlayer.style.top = newTop + 'px';
    }

    function isTargetInteractive(target) {
        if (!target || !target.closest) return false;
        return !!(
            target.closest('.mpu-progress-container') ||
            target.closest('button')
        );
    }

    function handleDragStart(event) {
        if (event.button !== 0 || isTargetInteractive(event.target) || document.fullscreenElement === miniPlayer) return;

        isDragging = true;
        dragStartX = event.clientX;
        dragStartY = event.clientY;

        const rect = miniPlayer.getBoundingClientRect();
        dragStartLeft = rect.left;
        dragStartTop = rect.top;

        document.addEventListener('mousemove', handleDragMove);
        document.addEventListener('mouseup', handleDragEnd);

        event.preventDefault();
    }

    function displayMiniPlayer() {
        if (miniPlayer && document.body.contains(miniPlayer)) return miniPlayer;
        miniPlayer = document.createElement('div');
        miniPlayer.className = CFG.miniPlayerClass;
        document.body.appendChild(miniPlayer);
        miniPlayer.addEventListener('mousedown', handleDragStart);
        miniPlayer.addEventListener('click', onMiniPlayerBackgroundClick);
        document.addEventListener('click', onTimestampClickPreserveScroll, true);

        requestAnimationFrame(() => {
            miniPlayer.classList.add('visible');
        });

        return miniPlayer;
    }

    function moveToMiniPlayer() {
        if (!rawVideo || !window.location.pathname.includes('watch')) return;

        const mainPlayer = document.querySelector(CFG.mainPlayerSelector);
        const activeVideo = mainPlayer ? mainPlayer.querySelector(CFG.rawVideoSelector) : null;
        const offlineSlate = mainPlayer ? mainPlayer.querySelector('.ytp-offline-slate') : null;
        if (!activeVideo || (offlineSlate && offlineSlate.style.display !== 'none')) return;
        if (isLive()) return;

        rawVideo = activeVideo;

        playerInitialLocation = rawVideo.nextSibling;
        displayMiniPlayer().appendChild(rawVideo);
        displayControls();
        attachControlListeners();
        attachVideoListeners();
        attachFullscreenListener();
        attachAutohideListeners();
        showControlsNow();
    }

    function toggleMiniPlayer(enabled) {
        if (!rawVideo) return;

        if (enabled) {
            moveToMiniPlayer();
        } else {
            restoreFromMiniPlayer();
        }
    }

    function handleVisibility(inView) {
        if (!inView) isPlayerHidden = false;
        if (isPlayerHidden && inView) return;
        toggleMiniPlayer(inView);
    }

    function tick() {
        if (!rawVideo) return;

        handleVisibility(window.scrollY > CFG.scrollThreshold);
        requestAnimationFrame(tick);
    }

    function checkVideoExists() {
        const mainPlayer = document.querySelector(CFG.mainPlayerSelector);
        const inWatchPage = window.location.pathname.includes('watch');
        const foundVideo = mainPlayer ? mainPlayer.querySelector(CFG.rawVideoSelector) : null;
        const newVideo = (inWatchPage && foundVideo) ? foundVideo : null;
        if (rawVideo !== newVideo) {
            rawVideo = newVideo;
        }
        if (!rawVideo) return;
        if (!loopStarted) {
            loopStarted = true;
            if (observer) observer.disconnect();
            tick();
        }
    }

    function init() {
        injectStyle();
        if (observer) observer.disconnect();
        observer = new MutationObserver(checkVideoExists);
        observer.observe(document.body, { childList: true, subtree: true });
    }

    init();
})();