X Reels

Includes central feed start, persistent overlay, robust scroll, follow main tweet author, like animation, and aggressive autoplay.

// ==UserScript==
// @name          X Reels
// @namespace     http://tampermonkey.net/
// @version       28.7.13 // Video controls now hide on idle and show on hover.
// @description   Includes central feed start, persistent overlay, robust scroll, follow main tweet author, like animation, and aggressive autoplay.
// @author        Kristijan1001
// @match         https://twitter.com/*
// @match         https://x.com/*
// @icon          https://www.google.com/s2/favicons?sz=64&domain=x.com
// @grant         none
// @license       MIT
// ==/UserScript==

(function() {
    'use strict';

    class TikTokFeed {
        constructor() {
            this.activePost = { id: null, element: null, mediaItems: [], currentMediaIndex: 0 };
            this.activePlaceholder = null;
            this.activeDisplayedMediaElement = null;
            this.isActive = false;
            this.isNavigating = false;
            this.lastScrollTime = 0;
            this.container = null;
            this.likeButton = null;
            this.followButton = null;
            this.exitButton = null;
            this.scrollUpButton = null; // New: Scroll Up button
            this.scrollDownButton = null; // New: Scroll Down button
            this.savedScrollPosition = 0;
            this.mutationObserver = null;
            this.isReturningFromFollow = false;

            // New properties for auto-scroll
            this.autoScrollDelay = parseInt(localStorage.getItem('xreels_autoScrollDelay') || '0', 10); // Default to 0 (Off)
            this.autoScrollTimeoutId = null;
            this.hideControlsTimeoutId = null; // New: for video controls hover
            this.hideInfoTimeoutId = null; // New: for info box hover

            this.boundHandleWheel = null;
            this.boundHandleKeydown = null;
            this.boundHandlePopState = null;
        }

        init() {
            console.log('🔥 X Reels Initializing...');
            if ('scrollRestoration' in history) {
                history.scrollRestoration = 'manual';
                console.log('History scroll restoration set to manual.');
            }
            this.setupEventListeners();
            setTimeout(() => this.addManualTrigger(), 1000); // Still show the manual trigger button on load
        }

        addManualTrigger() {
            let trigger = document.getElementById('tiktok-trigger');
            if (trigger) trigger.remove();
            trigger = document.createElement('div');
            trigger.id = 'tiktok-trigger';
            // Fancy "Feed Mode" button with pulse animation
            trigger.innerHTML = `
                <div style="display: flex; align-items: center; gap: 8px;">
                    <div style="width: 8px; height: 8px; background: #ff0050; border-radius: 50%; animation: pulse 2s infinite;"></div>
                    <span style="font-weight: 700;">X Reels</span>
                </div>
            `;
            trigger.style.cssText = `
                position: fixed; top: 20px; right: 20px; /* Moved back to top right */
                background: linear-gradient(135deg, #000 0%, #333 100%);
                color: white; padding: 10px 16px; border-radius: 20px;
                font-size: 13px; cursor: pointer; z-index: 1000000;
                box-shadow: 0 4px 20px rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.1);
                transition: all 0.3s ease; backdrop-filter: blur(10px);
                border: 1px solid rgba(255,255,255,0.1);
            `;

            // Add pulse and hover styles
            const style = document.createElement('style');
            style.textContent = `
                @keyframes pulse {
                    0%, 100% { opacity: 1; transform: scale(1); }
                    50% { opacity: 0.5; transform: scale(1.1); }
                }
                #tiktok-trigger:hover {
                    transform: translateY(-2px);
                    box-shadow: 0 6px 25px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.2);
                }
            `;
            document.head.appendChild(style);

            trigger.addEventListener('click', () => this.startFeed());
            document.body.appendChild(trigger);
        }

        startFeed() {
            if (this.isActive) { // Prevent starting if already active
                console.log('Feed is already active.');
                return;
            }

            document.querySelectorAll('video').forEach(vid => {
                if (!vid.paused) vid.pause();
                vid.muted = true;
            });

            const firstPostWithMedia = this.findCentralMediaArticle();
            if (!firstPostWithMedia) {
                console.error('❌ No media found on screen. Scroll down and try again.');
                return;
            }
            this.isActive = true;
            this.createContainer();
            this.navigateToPost(firstPostWithMedia);
            const trigger = document.getElementById('tiktok-trigger');
            if (trigger) trigger.style.display = 'none';
        }

        exit() {
            console.log('👋 Exiting feed...');
            this.isActive = false;
            this.stopAutoScrollTimer(); // Stop auto-scroll when exiting
            clearTimeout(this.hideControlsTimeoutId); // Clear any pending hide
            clearTimeout(this.hideInfoTimeoutId); // Clear any pending info hide

            // Ensure controls are hidden if a video was active
            if (this.activeDisplayedMediaElement && this.activeDisplayedMediaElement.tagName === 'VIDEO') {
                this.activeDisplayedMediaElement.controls = false;
            }

            // Hide loading indicator before container is removed
            this.hideLoadingIndicator();

            this.restoreOriginalMediaPosition(); // This will handle restoring media to original tweet

            // Restore original positions for all media items in the active post
            if (this.activePost && this.activePost.mediaItems) {
                this.activePost.mediaItems.forEach(item => {
                    if (item.type === 'video' && item.originalElement && item.placeholder && item.placeholder.parentElement) {
                        item.originalElement.style.cssText = '';
                        if (item.originalElement.parentNode !== item.placeholder.parentElement) {
                            item.placeholder.parentElement.replaceChild(item.originalElement, item.placeholder);
                        }
                        if (!item.originalElement.paused) {
                            item.originalElement.pause();
                        }
                    }
                });
            }

            if (this.container) {
                this.container.remove();
            }
            this.container = null;
            document.body.style.scrollBehavior = 'auto';
            const trigger = document.getElementById('tiktok-trigger');
            if (trigger) trigger.style.display = 'block';
            this.activePost = { id: null, element: null, mediaItems: [], currentMediaIndex: 0 };
            this.activeDisplayedMediaElement = null;
            this.activePlaceholder = null;
            this.likeButton = null;
            this.followButton = null;
            this.exitButton = null;
            this.scrollUpButton = null; // Reset buttons
            this.scrollDownButton = null; // Reset buttons
            this.savedScrollPosition = 0;
            this.disconnectObserver();
            this.isReturningFromFollow = false;
        }

        createContainer() {
            if (this.container) this.container.remove();
            this.container = document.createElement('div');
            this.container.id = 'tiktok-feed-container';
            // Main container should cover the full viewport and hide overflow
            this.container.style.cssText = `
                position: fixed; top: 0; left: 0;
                width: 100vw; height: 100vh;
                background: rgba(0, 0, 0, 0.90);
                z-index: 2147483647;
                pointer-events: none;
                overflow: hidden; /* Crucial to clip content to the viewport */
            `;
            // media-wrapper is the direct parent for media, using flexbox for centering
            this.container.innerHTML = `
                <div class="media-wrapper" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; pointer-events: auto; overflow: hidden;"></div>
                <div class="ui-container" style="position: relative; z-index: 1000002; width: 100%; height: 100%; pointer-events: none;">

                    <div class="loading-indicator" style="display: none; position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.8); color: white; padding: 8px 12px; border-radius: 15px; z-index: 1000003; font-size: 12px; font-weight: 600; backdrop-filter: blur(10px); pointer-events: none;">Loading...</div>

                    <div style="position: absolute; top: 20px; left: 20px; display: flex; flex-direction: column; align-items: flex-start; gap: 5px; pointer-events: auto;">
                        <div class="info-controls-wrapper" style="
                            color: rgba(255,255,255,0.9);
                            background: rgba(0,0,0,0.6);
                            padding: 6px 10px;
                            border-radius: 15px;
                            font-size: 12px;
                            backdrop-filter: blur(10px);
                            pointer-events: auto;
                            transition: all 0.3s ease;
                        ">
                            <div class="info-header" style="color: white; cursor: pointer; text-align: left; display: flex; align-items: center; justify-content: flex-start; gap: 5px;">Keyboard Shortcuts <span style="
                                    display: inline-block;
                                    width: 12px; height: 12px;
                                    background-image: url(&quot;data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%3Cpath%20d%3D%22M7.41%208.59L12%2013.17L16.59%208.59L18%2010L12%2016L6%2010L7.41%208.59Z%22%20fill%3D%22%23FF6F61%22%2F%3E%0A%3C%2Fsvg%3E&quot;);
                                    background-repeat: no-repeat;
                                    background-position: center;
                                    background-size: contain;
                                    transform: rotate(0deg);
                                    transition: transform 0.3s ease-out;
                            "></span></div>
                            <div class="controls-expanded" style="
                                max-height: 0;
                                overflow: hidden;
                                opacity: 0;
                                transition: max-height 0.3s ease-out, opacity 0.3s ease-out;
                                padding-top: 0;
                                font-weight: 400;
                            ">
                                • X: <span style="color:#FF6F61;">Enter/Exit Feed</span> <br>
                                • Scroll: <span style="color:#FF6F61;">⬆️/⬇️</span> <br>
                                • Cycle Media: <span style="color:#FF6F61;">⬅️/➡️</span> <br>
                                • Space: <span style="color:#FF6F61;">Play/Pause</span> <br>
                                • &lt; / &gt; : <span style="color:#FF6F61;">Scrub</span> <br>
                                • L: <span style="color:#FF6F61;">Like</span> <br>
                                • F: <span style="color:#FF6F61;">Follow</span> <br>
                                • Q: <span style="color:#FF6F61;">Exit</span>
                            </div>
                        </div>
                    </div>

                    <div style="position: absolute; top: 20px; right: 20px; display: flex; flex-direction: column; align-items: flex-end; gap: 5px; pointer-events: auto;">
                        <button id="tiktok-exit-btn" style="background: linear-gradient(135deg, #FF6F61 0%, #E63946 100%); border: none; border-radius: 15px; padding: 8px 12px; color: white; font-size: 12px; font-weight: 700; cursor: pointer; backdrop-filter: blur(10px); box-shadow: 0 2px 10px rgba(0,0,0,0.3); transition: all 0.2s ease; pointer-events: auto;">✕ Exit</button>
                        <div class="auto-scroll-settings" style="color: white; background: rgba(0,0,0,0.6); padding: 8px 12px; border-radius: 15px; font-size: 12px; font-weight: 600; backdrop-filter: blur(10px); display: flex; align-items: center; gap: 5px; pointer-events: auto;">
                            Auto-scroll: <select id="auto-scroll-select" style="background: rgba(0,0,0,0.8); border: none; color: #FF6F61; font-size: 12px; font-weight: 600; padding-right: 15px; outline: none; cursor: pointer; -webkit-appearance: none; -moz-appearance: none; appearance: none; border-radius: 5px;
                                background-image: url(&quot;data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%3Cpath%20d%3D%22M7.41%208.59L12%2013.17L16.59%208.59L18%2010L12%2016L6%2010L7.41%208.59Z%22%20fill%3D%22%23FF6F61%22%2F%3E%0A%3C%2Fsvg%3E&quot;);
                                background-repeat: no-repeat;
                                background-position: right 5px center;
                                background-size: 12px; /* Adjust size of arrow */
                                ">
                                <option value="0" style="background: black; color: #FF6F61;">Off</option>
                                <option value="1000" style="background: black; color: white;">1s</option>
                                <option value="2000" style="background: black; color: white;">2s</option>
                                <option value="3000" style="background: black; color: white;">3s</option>
                                <option value="5000" style="background: black; color: white;">5s</option>
                                <option value="8000" style="background: black; color: white;">8s</option>
                                <option value="30000" style="background: black; color: white;">30s</option>
                                <option value="60000" style="background: black; color: white;">60s</option>
                            </select>
                        </div>
                    </div>

                    <div class="gallery-indicator" style="position: absolute; top: 20px; left: 50%; transform: translateX(-50%); color: white; background: rgba(0,0,0,0.8); padding: 6px 12px; border-radius: 15px; font-size: 14px; font-weight: 700; backdrop-filter: blur(10px); display: none; pointer-events: none;"></div>

                    <div class="info" style="position: absolute; bottom: 20px; left: 20px; right: 20px; color: white; background: rgba(0,0,0,0.0); padding: 20px; border-radius: 16px; max-height: 120px; overflow-y: auto; backdrop-filter: blur(0px); pointer-events: auto;"></div>

                    <div class="action-buttons" style="position: absolute; right: 20px; bottom: 20px; display: flex; flex-direction: column; gap: 12px; z-index: 1000002; pointer-events: auto;">
                        <button id="tiktok-like-btn" style="background: rgba(255,255,255,0.2); border: none; border-radius: 50%; width: 45px; height: 45px; color: white; font-size: 20px; cursor: pointer; backdrop-filter: blur(10px); box-shadow: 0 2px 10px rgba(0,0,0,0.3); transition: all 0.3s ease; display: flex; align-items: center; justify-content: center;">♡</button>
                        <button id="tiktok-follow-btn" style="background: rgba(255,255,255,0.2); border: none; border-radius: 50%; width: 45px; height: 45px; color: white; font-size: 20px; cursor: pointer; backdrop-filter: blur(10px); box-shadow: 0 2px 10px rgba(0,0,0,0.3); transition: all 0.3s ease; display: flex; align-items: center; justify-content: center;">➕</button>
                        <button id="tiktok-scroll-up-btn" style="background: rgba(255,255,255,0.2); border: none; border-radius: 50%; width: 45px; height: 45px; color: white; font-size: 20px; cursor: pointer; backdrop-filter: blur(10px); box-shadow: 0 2px 10px rgba(0,0,0,0.3); transition: all 0.3s ease; display: flex; align-items: center; justify-content: center;">⬆️</button>
                        <button id="tiktok-scroll-down-btn" style="background: rgba(255,255,255,0.2); border: none; border-radius: 50%; width: 45px; height: 45px; color: white; font-size: 20px; cursor: pointer; backdrop-filter: blur(10px); box-shadow: 0 2px 10px rgba(0,0,0,0.3); transition: all 0.3s ease; display: flex; align-items: center; justify-content: center;">⬇️</button>
                    </div>
                </div>
            `;
            document.body.appendChild(this.container);
            this.likeButton = this.container.querySelector('#tiktok-like-btn');
            this.followButton = this.container.querySelector('#tiktok-follow-btn');
            this.exitButton = this.container.querySelector('#tiktok-exit-btn');
            this.scrollUpButton = this.container.querySelector('#tiktok-scroll-up-btn'); // Get new button
            this.scrollDownButton = this.container.querySelector('#tiktok-scroll-down-btn'); // Get new button

            this.likeButton.addEventListener('click', () => this.toggleLike());
            this.followButton.addEventListener('click', () => this.toggleFollow());
            this.exitButton.addEventListener('click', () => this.exit());

            // Add event listeners for the new info/controls section
            const infoControlsWrapper = this.container.querySelector('.info-controls-wrapper');
            const controlsExpanded = this.container.querySelector('.controls-expanded');
            const infoHeader = this.container.querySelector('.info-header'); // Get the info header
            const infoArrow = infoHeader.querySelector('span'); // Get the arrow span

            let isHoveringInfo = false; // To track hover state
            let isInfoExpanded = false; // To track if info is expanded

            // Mouse enter to expand
            infoControlsWrapper.addEventListener('mouseenter', () => {
                isHoveringInfo = true;
                clearTimeout(this.hideInfoTimeoutId); // Clear any pending hide
                if (!isInfoExpanded) {
                    controlsExpanded.style.maxHeight = '200px'; // A value larger than the content
                    controlsExpanded.style.opacity = '1';
                    controlsExpanded.style.paddingTop = '8px'; // Add padding after expansion
                    infoArrow.style.transform = 'rotate(180deg)'; // Rotate arrow up
                    isInfoExpanded = true;
                }
            });

            // Mouse leave to collapse with a slight delay
            infoControlsWrapper.addEventListener('mouseleave', () => {
                isHoveringInfo = false;
                this.hideInfoTimeoutId = setTimeout(() => {
                    if (!isHoveringInfo) { // Only hide if mouse is truly not hovering
                        controlsExpanded.style.maxHeight = '0';
                        controlsExpanded.style.opacity = '0';
                        controlsExpanded.style.paddingTop = '0';
                        infoArrow.style.transform = 'rotate(0deg)'; // Rotate arrow back down
                        isInfoExpanded = false;
                    }
                }, 500); // Small delay before hiding
            });

            // Corrected button listeners to prioritize media navigation
            this.scrollUpButton.addEventListener('click', () => {
                const mediaNavigated = this.handleMediaNavigation('prev');
                if (!mediaNavigated) {
                    this.handlePostNavigation('prev');
                }
            });
            this.scrollDownButton.addEventListener('click', () => {
                const mediaNavigated = this.handleMediaNavigation('next');
                if (!mediaNavigated) {
                    this.handlePostNavigation('next');
                }
            });

            this.setupAutoScrollControls();
        }

        setupAutoScrollControls() {
            const selectElement = this.container.querySelector('#auto-scroll-select');
            if (selectElement) {
                // Set initial value from localStorage
                selectElement.value = this.autoScrollDelay.toString();

                selectElement.addEventListener('change', (e) => {
                    this.autoScrollDelay = parseInt(e.target.value, 10);
                    localStorage.setItem('xreels_autoScrollDelay', this.autoScrollDelay.toString());
                    console.log(`Auto-scroll set to: ${this.autoScrollDelay / 1000}s`);
                    // Only start timer if feed is active and a delay is selected
                    if (this.isActive && this.autoScrollDelay > 0) {
                        this.startAutoScrollTimer();
                    } else if (this.autoScrollDelay === 0) {
                        this.stopAutoScrollTimer();
                    }
                });
            }
        }

        startAutoScrollTimer() {
            this.stopAutoScrollTimer(); // Clear any existing timer
            if (this.isActive && this.autoScrollDelay > 0) {
                this.autoScrollTimeoutId = setTimeout(() => {
                    // PRIORITIZE media navigation within the current post
                    const mediaNavigated = this.handleMediaNavigation('next'); // Returns true if navigated media, false otherwise

                    if (!mediaNavigated) {
                        // If no more media in current post, then move to the next post
                        this.handlePostNavigation('next');
                    }
                    // The timer will be restarted by displayCurrentMediaItem (which is called by handleMediaNavigation or navigateToPost).
                }, this.autoScrollDelay);
            }
        }

        stopAutoScrollTimer() {
            if (this.autoScrollTimeoutId) {
                clearTimeout(this.autoScrollTimeoutId);
                this.autoScrollTimeoutId = null;
            }
        }

        async toggleLike() {
            if (!this.activePost.element) return;

            this.likeButton.style.transform = 'scale(1.3)';
            this.likeButton.style.filter = 'brightness(1.5)';
            this.likeButton.style.transition = 'transform 0.1s ease-out, filter 0.1s ease-out';

            const likeButton = this.activePost.element.querySelector('[data-testid="like"], [data-testid="unlike"]');
            if (likeButton) {
                likeButton.click();
                await new Promise(resolve => setTimeout(resolve, 250));
                this.updateLikeButtonState();
            }

            setTimeout(() => {
                this.likeButton.style.transform = 'scale(1)';
                this.likeButton.style.filter = 'brightness(1)';
                this.likeButton.style.transition = 'all 0.3s ease';
            }, 200);
        }

        async toggleFollow() {
            if (!this.activePost.element) return;

            const originalTweetId = this.activePost.id;
            const originalMediaIndex = this.activePost.currentMediaIndex;

            this.savedScrollPosition = window.scrollY;
            this.isReturningFromFollow = true;
            console.log(`Saved scroll position: ${this.savedScrollPosition} for tweet ${originalTweetId}`);

            let userLinkToClick = null;

            // Simplified: Always target the main author of the currently active post for follow.
            // Removed specific quote tweet author detection logic for simplicity and reliability.
            userLinkToClick = this.activePost.element.querySelector('[data-testid="User-Name"] a[href*="/"][role="link"]');
            console.log("Following main tweet author of the displayed post.");

            if (!userLinkToClick) {
                console.warn("Could not find user link for follow action.");
                this.isReturningFromFollow = false;
                return;
            }

            console.log(`Following media author: ${userLinkToClick.href}`);
            userLinkToClick.click();
            await new Promise(resolve => setTimeout(resolve, 1500));

            let followBtn = null;
            let attempts = 0;
            const maxAttempts = 15;
            while (!followBtn && attempts < maxAttempts) {
                // This selector correctly finds both "Follow" and "Unfollow" buttons on the profile page
                followBtn = document.querySelector('[data-testid*="follow"], [aria-label*="Follow"], [aria-label*="Unfollow"]');
                if (!followBtn) {
                    await new Promise(resolve => setTimeout(resolve, 200));
                }
                attempts++;
            }

            if (followBtn) {
                followBtn.click();
                await new Promise(resolve => setTimeout(resolve, 500));
            } else {
                console.warn("Follow/Unfollow button not found on profile page.");
            }

            window.history.back();
            await new Promise(resolve => setTimeout(resolve, 1500));

            this.showLoadingIndicator('Restoring feed position...');
            let targetPostElement = null;
            attempts = 0;
            const checkInterval = 200;
            const maxChecks = 25;

            while (!targetPostElement && attempts < maxChecks) {
                targetPostElement = document.querySelector(`article[role="article"] a[href*="/status/${originalTweetId}"]`)?.closest('article[role="article"]');
                if (!targetPostElement) {
                    await new Promise(resolve => setTimeout(resolve, checkInterval));
                }
                attempts++;
            }

            if (targetPostElement) {
                console.log(`Found original tweet element (ID: ${originalTweetId}), re-navigating...`);
                const foundMediaItems = [];
                targetPostElement.querySelectorAll('div[data-testid="videoPlayer"] video, div[data-testid="tweetPhoto"] img').forEach(mediaEl => {
                    if (mediaEl.tagName === 'VIDEO' && this.isValidMedia(mediaEl)) {
                        foundMediaItems.push({ type: 'video', originalElement: mediaEl, src: mediaEl.src, placeholder: null });
                    } else if (mediaEl.tagName === 'IMG' && !mediaEl.src.includes('video_thumb') && this.isValidMedia(mediaEl)) {
                        foundMediaItems.push({ type: 'image', originalElement: mediaEl, src: this.getFullQualityImageUrl(mediaEl), placeholder: null });
                    }
                });

                if (foundMediaItems.length > 0) {
                    this.activePost = {
                        id: originalTweetId,
                        element: targetPostElement,
                        mediaItems: foundMediaItems,
                        currentMediaIndex: Math.min(originalMediaIndex, foundMediaItems.length - 1)
                    };
                    this.attemptScrollToPost(targetPostElement);
                    this.displayCurrentMediaItem();
                } else {
                    console.warn(`No media found in the original tweet element (ID: ${originalTweetId}) after returning. Falling back to central post.`);
                    this.navigateToPost(this.findCentralMediaArticle());
                }
            } else {
                console.warn(`Original tweet element (ID: ${originalTweetId}) not found after returning. Falling back to central post.`);
                this.navigateToPost(this.findCentralMediaArticle());
            }

            window.scrollTo(0, this.savedScrollPosition);
            this.hideLoadingIndicator();
            this.isReturningFromFollow = false;
        }

        updateLikeButtonState() {
            if (!this.likeButton || !this.activePost.element) return;
            const likedButton = this.activePost.element.querySelector('[data-testid="unlike"]');
            if (likedButton) {
                this.likeButton.style.background = 'linear-gradient(135deg, #ff1744 0%, #d50000 100%)';
                this.likeButton.innerHTML = '❤️';
            } else {
                this.likeButton.style.background = 'rgba(255,255,255,0.2)';
                this.likeButton.innerHTML = '♡';
            }
        }

        updateFollowButtonState() {
            if (!this.followButton || !this.activePost.element) return;

            let isFollowingTargetAuthor = false;

            // This logic will now only check the follow status of the MAIN author of the current post
            const mainAuthorUnfollowBtn = this.activePost.element.querySelector('[data-testid$="-unfollow"]');
            if (mainAuthorUnfollowBtn) {
                isFollowingTargetAuthor = true;
            } else {
                const mainAuthorFollowingText = Array.from(this.activePost.element.querySelectorAll('[data-testid="User-Name"] ~ div button')).some(btn => {
                    const text = btn.textContent.trim();
                    return text === 'Following' || text === 'Unfollow';
                });
                if (mainAuthorFollowingText) {
                    isFollowingTargetAuthor = true;
                }
            }
            // Removed quote tweet specific follow status check for simplicity.

            if (isFollowingTargetAuthor) {
                this.followButton.style.background = 'linear-gradient(135deg, #4CAF50 0%, #388E3C 100%)';
                this.followButton.innerHTML = '✔️';
            } else {
                this.followButton.style.background = 'rgba(255,255,255,0.2)';
                this.followButton.innerHTML = '➕';
            }
        }

        updateUI() {
            if (!this.container || !this.activePost || !this.activePost.element) return;

            let authorName = 'Unknown User';
            let tweetText = this.activePost.element.querySelector('[data-testid="tweetText"]')?.textContent || '';

            const quotedTweetArticle = this.activePost.element.querySelector('article[tabindex="-1"]');
            if (quotedTweetArticle) {
                const quotedAuthorNameElement = quotedTweetArticle.querySelector('[data-testid="User-Name"] [dir="ltr"]');
                if (quotedAuthorNameElement) {
                    authorName = `(QT) ${quotedAuthorNameElement.textContent}`;
                }
                if (!tweetText) {
                    tweetText = quotedTweetArticle.querySelector('[data-testid="tweetText"]')?.textContent || '';
                }
            }

            if (authorName === 'Unknown User' || authorName.startsWith('(QT) ') === false) {
                const mainAuthorNameElement = this.activePost.element.querySelector('[data-testid="User-Name"] [dir="ltr"]');
                if (mainAuthorNameElement) {
                    authorName = mainAuthorNameElement.textContent;
                }
            }

            this.container.querySelector('.info').innerHTML = `<div style="font-weight: 700; font-size: 16px; margin-bottom: 8px;">${authorName}</div><div style="font-size: 14px; line-height: 1.4; opacity: 0.9;">${tweetText}</div>`;

            const galleryIndicator = this.container.querySelector('.gallery-indicator');
            if (this.activePost.mediaItems.length > 1) {
                galleryIndicator.textContent = `${this.activePost.currentMediaIndex + 1} / ${this.activePost.mediaItems.length}`;
                galleryIndicator.style.display = 'block';
            } else {
                galleryIndicator.style.display = 'none';
            }
            this.updateLikeButtonState();
            this.updateFollowButtonState();
        }

        showLoadingIndicator(message = 'Loading...') {
            // Added null check for this.container
            if (this.container) {
                const indicator = this.container.querySelector('.loading-indicator');
                if(indicator) { indicator.textContent = message; indicator.style.display = 'block'; }
            }
        }

        hideLoadingIndicator() {
            // Added null check for this.container
            if (this.container) {
                const indicator = this.container.querySelector('.loading-indicator');
                if(indicator) indicator.style.display = 'none';
            }
        }

        navigateToPost(targetArticleElement) {
            if (!targetArticleElement || this.isNavigating) return;
            this.isNavigating = true;
            this.stopAutoScrollTimer(); // Stop current timer when navigating manually or to a new post
            this.restoreOriginalMediaPosition();
            this.showLoadingIndicator('Loading...');

            const postTweetId = this.generateTweetId(targetArticleElement);
            const foundMediaItems = [];
            const addedElements = new Set();
            targetArticleElement.querySelectorAll('div[data-testid="videoPlayer"] video, div[data-testid="tweetPhoto"] img').forEach(mediaEl => {
                if (addedElements.has(mediaEl)) return;
                if (mediaEl.tagName === 'VIDEO' && this.isValidMedia(mediaEl)) {
                    foundMediaItems.push({ type: 'video', originalElement: mediaEl, src: mediaEl.src, placeholder: null });
                    addedElements.add(mediaEl);
                } else if (mediaEl.tagName === 'IMG' && !mediaEl.src.includes('video_thumb') && this.isValidMedia(mediaEl)) {
                    foundMediaItems.push({ type: 'image', originalElement: mediaEl, src: this.getFullQualityImageUrl(mediaEl), placeholder: null });
                    addedElements.add(mediaEl);
                }
            });

            if (foundMediaItems.length === 0) {
                console.warn('No valid media found in the target article, skipping to next.');
                this.isNavigating = false;
                this.hideLoadingIndicator(); // This call is now safe due to the added null check
                this.handlePostNavigation('next'); // Immediately try next post if current has no media
                return;
            }

            this.activePost = { id: postTweetId, element: targetArticleElement, mediaItems: foundMediaItems, currentMediaIndex: 0 };
            document.body.style.scrollBehavior = 'auto'; // This ensures the outer body scroll is instant, not smooth
            this.attemptScrollToPost(targetArticleElement);

            setTimeout(() => {
                this.displayCurrentMediaItem();
                this.hideLoadingIndicator(); // This call is now safe due to the added null check
                this.isNavigating = false;
                // Removed redundant startAutoScrollTimer call here. It's now handled by displayCurrentMediaItem.
            }, 650);
        }

        async attemptScrollToPost(element, attempts = 0, maxAttempts = 10, interval = 100) {
            if (!element || attempts >= maxAttempts) {
                console.warn('Failed to scroll post into view after multiple attempts.');
                return;
            }
            // Changed behavior to 'auto' for instant scroll
            element.scrollIntoView({ behavior: 'auto', block: 'center' });
            await new Promise(resolve => setTimeout(resolve, interval));
            if (!this.isElementInViewport(element)) {
                this.attemptScrollToPost(element, attempts + 1, maxAttempts, interval);
            }
        }

        isElementInViewport(el) {
            if (!el) return false;
            const rect = el.getBoundingClientRect();
            const margin = 50; // Added a small margin to consider it "in viewport"
            return (
                rect.top >= -margin &&
                rect.left >= -margin &&
                rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) + margin &&
                rect.right <= (window.innerWidth || document.documentElement.clientWidth) + margin
            );
        }

        displayCurrentMediaItem() {
            if (!this.activePost || !this.activePost.mediaItems || this.activePost.mediaItems.length === 0) return;
            this.restoreOriginalMediaPosition();

            const mediaItemToDisplay = this.activePost.mediaItems[this.activePost.currentMediaIndex];
            if (!mediaItemToDisplay) return;

            this.updateUI();

            const mediaWrapper = this.container.querySelector('.media-wrapper');
            mediaWrapper.innerHTML = ''; // Clear previous media

            if (mediaItemToDisplay.type === 'video') {
                this.displayVideo(mediaItemToDisplay);
            } else {
                this.displayImage(mediaItemToDisplay);
            }

            // crucial fix: restart auto-scroll timer after displaying any media item
            this.startAutoScrollTimer();
        }

        displayVideo(mediaItem) {
            const mediaWrapper = this.container.querySelector('.media-wrapper');
            const videoElement = mediaItem.originalElement;

            if (!mediaItem.placeholder) {
                const placeholder = document.createElement('div');
                const rect = videoElement.getBoundingClientRect();
                placeholder.style.width = `${rect.width}px`;
                placeholder.style.height = `${rect.height}px`;
                mediaItem.placeholder = placeholder;

                if (videoElement.parentElement) {
                    videoElement.parentElement.replaceChild(placeholder, videoElement);
                }
            }

            this.activeDisplayedMediaElement = videoElement;

            // CSS for video: contain within container with black background
            videoElement.style.cssText = `
                width: 100%;
                height: 100%;
                object-fit: contain; /* Ensured 'contain' to prevent cropping/cutting out */
                border-radius: 0;
                display: block;
                background: black; /* Added background to fill empty space */
            `;
            videoElement.loop = true;
            videoElement.muted = true;
            videoElement.controls = false; // Hide controls initially

            // Add hover listeners to mediaWrapper for video controls
            mediaWrapper.addEventListener('mouseenter', () => {
                if (this.activeDisplayedMediaElement && this.activeDisplayedMediaElement.tagName === 'VIDEO') {
                    clearTimeout(this.hideControlsTimeoutId); // Clear any pending hide
                    this.activeDisplayedMediaElement.controls = true;
                }
            });

            mediaWrapper.addEventListener('mouseleave', () => {
                if (this.activeDisplayedMediaElement && this.activeDisplayedMediaElement.tagName === 'VIDEO') {
                    // Set a small delay before hiding to prevent flickering
                    this.hideControlsTimeoutId = setTimeout(() => {
                        if (this.activeDisplayedMediaElement && this.activeDisplayedMediaElement.tagName === 'VIDEO') { // Re-check in case element changed
                            this.activeDisplayedMediaElement.controls = false;
                        }
                    }, 500); // Hide after 500ms
                }
            });


            mediaWrapper.appendChild(videoElement); // Append to mediaWrapper as originally designed

            this.attemptVideoPlay(videoElement);

            videoElement.addEventListener('click', () => {
                if (videoElement.paused) {
                    this.attemptVideoPlay(videoElement);
                } else {
                    videoElement.pause();
                }
            });

            videoElement.addEventListener('dblclick', (e) => {
                e.stopPropagation();
                videoElement.muted = !videoElement.muted;
                if (!videoElement.muted) {
                    videoElement.volume = 0.7;
                } else {
                    videoElement.volume = 0;
                }
            });
        }

        async simulateTwitterPlayClick(vidEl) {
            const videoPlayerContainer = vidEl.closest('div[data-testid="videoPlayer"]');
            if (videoPlayerContainer) {
                let playButton = videoPlayerContainer.querySelector(
                    'svg[role="img"][aria-label="Play"], ' +
                    'div[role="button"][tabindex="0"][aria-label*="Play"], ' +
                    'div[data-testid="playButton"], ' +
                    'div[data-testid="SensitiveMediaDisclosure"] button, ' +
                    'div[data-testid="videoPlayer"] > div > div > div[tabindex="0"], ' +
                    'div[data-testid="tweetPhoto"] div[role="button"], ' +
                    '[data-testid="ShyPinButton"], ' +
                    'div[data-testid="play-pause-button"]'
                );

                if (!playButton) {
                    playButton = videoPlayerContainer;
                    console.log('No specific play button found, falling back to clicking video container.');
                }

                if (playButton) {
                    console.log('Attempting to simulate click on Twitter\'s video player button/container:', playButton);
                    playButton.click();
                    await new Promise(resolve => setTimeout(resolve, 100));
                    return true;
                }
            }
            return false;
        }

        async attemptVideoPlay(video) {
            let playAttempts = 0;
            const maxPlayAttempts = 15;
            const attemptInterval = 500;

            while (playAttempts < maxPlayAttempts) {
                if (!this.isActive || this.activeDisplayedMediaElement !== video) {
                    console.log('Video no longer active or feed exited, stopping autoplay attempts.');
                    video.pause();
                    return;
                }

                if (!video.paused && video.muted) {
                    console.log('Video is playing but muted, attempting to unmute.');
                    video.muted = false;
                    video.volume = 0.7;
                    await new Promise(resolve => setTimeout(resolve, 200));
                    if (!video.muted) {
                        console.log('Video successfully unmuted.');
                        return;
                    }
                }

                if (!video.paused && !video.muted) {
                    console.log('Video is playing and unmuted. Autoplay successful.');
                    return;
                }

                console.log(`Autoplay attempt ${playAttempts + 1}/${maxPlayAttempts} for video. State: paused=${video.paused}, muted=${video.muted}`);

                if (video.paused) {
                    try {
                        video.currentTime = 0;
                        video.muted = true;
                        await video.play();
                        console.log('Direct muted play attempt successful.');
                        video.muted = false;
                        video.volume = 0.7;
                    } catch (error) {
                        console.warn(`Direct play failed (attempt ${playAttempts + 1}):`, error.name, error.message);
                        await this.simulateTwitterPlayClick(video);
                    }
                } else {
                    await this.simulateTwitterPlayClick(video);
                }

                playAttempts++;
                await new Promise(resolve => setTimeout(resolve, attemptInterval));
            }

            console.warn('All aggressive autoplay attempts exhausted. Video might require manual interaction.');
            video.controls = true;
            video.muted = false;
        }

        displayImage(mediaItem) {
            const mediaWrapper = this.container.querySelector('.media-wrapper');
            const img = document.createElement('img');
            img.src = mediaItem.src;
            // CSS for image: contain within container with black background
            img.style.cssText = `
                width: 100%;
                height: 100%;
                object-fit: contain; /* Ensures entire image is visible, adds black bars */
                border-radius: 0;
                background: black; /* To make the bars black */
            `;
            mediaWrapper.appendChild(img); // Append to mediaWrapper as originally designed
            this.activeDisplayedMediaElement = img;
        }

        restoreOriginalMediaPosition() {
            if (this.activeDisplayedMediaElement) {
                if (this.activeDisplayedMediaElement.tagName === 'VIDEO' || this.activeDisplayedMediaElement.tagName === 'IMG') {
                    // Clear the applied styles
                    this.activeDisplayedMediaElement.style.cssText = '';

                    // Re-append to its original placeholder if it exists and still has a parent
                    const currentMediaItemObject = this.activePost.mediaItems.find(item => item.originalElement === this.activeDisplayedMediaElement);
                    if (currentMediaItemObject && currentMediaItemObject.placeholder && currentMediaItemObject.placeholder.parentNode) {
                        currentMediaItemObject.placeholder.parentElement.replaceChild(this.activeDisplayedMediaElement, currentMediaItemObject.placeholder);
                    } else if (this.activeDisplayedMediaElement.parentNode) {
                        // Fallback: if placeholder is gone, just remove from current parent
                        this.activeDisplayedMediaElement.remove();
                    }
                }
            }
            this.activeDisplayedMediaElement = null;
        }

        handlePostNavigation(direction) {
            if (this.isNavigating) return;

            let nextPostElement = null;
            if (direction === 'prev') {
                nextPostElement = this.findNextMediaArticle(this.activePost.element, true);
            } else {
                nextPostElement = this.findNextMediaArticle(this.activePost.element, false);
            }

            if (nextPostElement) {
                this.navigateToPost(nextPostElement);
            } else {
                this.showLoadingIndicator('No more visible posts. Scrolling to load more...');
                window.scrollTo({ top: document.body.scrollHeight, behavior: 'auto' }); // Changed to 'auto' for instant load scroll

                setTimeout(() => {
                    this.hideLoadingIndicator();
                    const newlyLoadedPost = this.findNextMediaArticle(this.activePost.element, false);
                    if (newlyLoadedPost && newlyLoadedPost !== this.activePost.element) {
                        this.navigateToPost(newlyLoadedPost);
                    } else {
                        console.log('No new media posts found after scrolling.');
                    }
                }, 2000); // Keep a delay here to allow content to load after scroll
            }
        }

        handleMediaNavigation(direction) {
            if (this.isNavigating || !this.activePost || !this.activePost.mediaItems || this.activePost.mediaItems.length <= 1) {
                return false;
            }
            let newMediaIndex = this.activePost.currentMediaIndex;
            let mediaNavigated = false;

            if (direction === 'next') {
                if (newMediaIndex < this.activePost.mediaItems.length - 1) {
                    newMediaIndex++;
                    this.activePost.currentMediaIndex = newMediaIndex;
                    this.displayCurrentMediaItem();
                    mediaNavigated = true;
                }
            } else if (direction === 'prev') {
                if (newMediaIndex > 0) {
                    newMediaIndex--;
                    this.activePost.currentMediaIndex = newMediaIndex;
                    this.displayCurrentMediaItem();
                    mediaNavigated = true;
                }
            }
            return mediaNavigated;
        }

        findNextMediaArticle(currentArticle, findPrevious = false) {
            const allArticles = Array.from(document.querySelectorAll('article[role="article"]'));
            const currentIndex = currentArticle ? allArticles.indexOf(currentArticle) : -1;
            let startIndex = findPrevious ? currentIndex - 1 : currentIndex + 1;
            let endIndex = findPrevious ? -1 : allArticles.length;
            let step = findPrevious ? -1 : 1;

            for (let i = startIndex; findPrevious ? i >= endIndex : i < endIndex; i += step) {
                const article = allArticles[i];
                if (article.querySelector('div[data-testid="videoPlayer"] video, div[data-testid="tweetPhoto"] img')) {
                    const mediaEls = article.querySelectorAll('div[data-testid="videoPlayer"] video, div[data-testid="tweetPhoto"] img');
                    for (const el of mediaEls) {
                        if (this.isValidMedia(el)) {
                            return article;
                        }
                    }
                }
            }
            return null;
        }

        findCentralMediaArticle() {
            const allArticles = Array.from(document.querySelectorAll('article[role="article"]'));
            const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
            const viewportCenter = viewportHeight / 2;
            let closestArticle = null;
            let minDistance = Infinity;

            for (const article /*: HTMLElement*/ of allArticles) {
                const mediaElements = article.querySelectorAll('div[data-testid="videoPlayer"] video, div[data-testid="tweetPhoto"] img');
                let hasValidMedia = false;
                for (const el of mediaElements) {
                    if (this.isValidMedia(el)) {
                        hasValidMedia = true;
                        break;
                    }
                }

                if (hasValidMedia) {
                    const rect = article.getBoundingClientRect();
                    if (rect.bottom > 0 && rect.top < viewportHeight) {
                        const articleCenter = rect.top + rect.height / 2;
                        const distance = Math.abs(viewportCenter - articleCenter);
                        if (distance < minDistance) {
                            minDistance = distance;
                            closestArticle = article;
                        }
                    }
                }
            }
            return closestArticle;
        }

        getFullQualityImageUrl(imgElement) {
            if (!imgElement || !imgElement.src) return null;
            let src = imgElement.src;
            if (src.includes('pbs.twimg.com/media/')) {
                const url = new URL(src);
                url.searchParams.set('name', 'orig');
                src = url.href;
            }
            return src;
        }

        generateTweetId(tweet) {
            if (!tweet) return null;
            const link = tweet.querySelector('a[href*="/status/"][dir="ltr"], a[href*="/status/"]');
            if (link) {
                const match = link.href.match(/\/status\/(\d+)/);
                if (match && match[1]) return match[1];
            }
            return null;
        }

        isValidMedia(element) {
            if (!element) return false;

            if (element.tagName === 'VIDEO') {
                const container = element.closest('div[data-testid="videoPlayer"]');
                return container && element.readyState >= 0;
            } else if (element.tagName === 'IMG') {
                return element.src && element.src.includes('pbs.twimg.com/media/') && !element.src.includes('video_thumb');
            }
            return false;
        }

        setupEventListeners() {
            if (this.boundHandleWheel) {
                document.removeEventListener('wheel', this.boundHandleWheel, { passive: false, capture: true });
                document.removeEventListener('keydown', this.boundHandleKeydown, { capture: true });
                window.removeEventListener('popstate', this.boundHandlePopState);
            }

            this.boundHandleWheel = this.handleWheel.bind(this);
            this.boundHandleKeydown = this.handleKeydown.bind(this);
            this.boundHandlePopState = this.handlePopState.bind(this);

            document.addEventListener('wheel', this.boundHandleWheel, { passive: false, capture: true });
            document.addEventListener('keydown', this.boundHandleKeydown, { capture: true });
            window.addEventListener('popstate', this.boundHandlePopState);
        }

        handleWheel(e) {
            if (!this.isActive) return;
            e.preventDefault();
            e.stopPropagation();

            if (Date.now() - this.lastScrollTime < 300) return; // Debounce
            this.lastScrollTime = Date.now();

            // Prioritize media navigation within a carousel, then post navigation
            const direction = e.deltaY > 0 ? 'next' : 'prev';
            const mediaNavigated = this.handleMediaNavigation(direction);

            if (!mediaNavigated) {
                this.handlePostNavigation(direction);
            }
        }

        handleKeydown(e) {
            // Handle 'X' as a toggle for the feed itself (Enter/Exit)
            if (e.key.toLowerCase() === 'x') {
                e.preventDefault();
                e.stopImmediatePropagation();
                if (this.isActive) {
                    this.exit(); // Exit if active
                } else {
                    this.startFeed(); // Start if inactive
                }
                return; // Consume the 'X' event
            }

            // If the feed is not active (and 'X' wasn't pressed), do nothing further with hotkeys
            if (!this.isActive) {
                return;
            }

            // Allow typing in input/textarea fields (except for 'Q' to exit)
            if (document.activeElement && (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA')) {
                // Special case: if in input/textarea, Q should still exit the feed
                if (e.key.toLowerCase() === 'q') {
                    e.preventDefault();
                    e.stopImmediatePropagation();
                    this.exit();
                }
                return; // Do not process other hotkeys if typing
            }

            // Spacebar logic: ONLY for video play/pause
            if (e.key === ' ') {
                e.preventDefault(); // Prevent page scroll
                e.stopPropagation(); // Stop event propagation

                if (this.activeDisplayedMediaElement && this.activeDisplayedMediaElement.tagName === 'VIDEO') {
                    if (this.activeDisplayedMediaElement.paused) {
                        this.activeDisplayedMediaElement.play();
                    } else {
                        this.activeDisplayedMediaElement.pause();
                    }
                }
                return; // Always consume spacebar event, whether video is present or not
            }

            // Video scrubbing with < and >
            if (this.activeDisplayedMediaElement && this.activeDisplayedMediaElement.tagName === 'VIDEO') {
                const video = this.activeDisplayedMediaElement;
                const scrubAmount = 5; // seconds

                if (e.key === ',' || e.key === '<') { // Comma or <
                    e.preventDefault();
                    e.stopPropagation();
                    video.currentTime = Math.max(0, video.currentTime - scrubAmount);
                    return;
                } else if (e.key === '.' || e.key === '>') { // Period or >
                    e.preventDefault();
                    e.stopPropagation();
                    video.currentTime = Math.min(video.duration, video.currentTime + scrubAmount);
                    return;
                }
            }


            // Other hotkeys (Q: Exit, Arrows: Navigate, L: Like, F: Follow)
            const keyMap = {
                'q': () => this.exit(),
                'Q': () => this.exit(),
                'ArrowUp': () => { e.preventDefault(); e.stopPropagation(); this.handlePostNavigation('prev'); },
                'ArrowDown': () => { e.preventDefault(); e.stopPropagation(); this.handlePostNavigation('next'); },
                'ArrowLeft': () => { e.preventDefault(); e.stopPropagation(); this.handleMediaNavigation('prev'); },
                'ArrowRight': () => { e.preventDefault(); e.stopPropagation(); this.handleMediaNavigation('next'); },
                'l': () => { e.preventDefault(); e.stopPropagation(); this.toggleLike(); },
                'L': () => { e.preventDefault(); e.stopPropagation(); this.toggleLike(); },
                'f': () => { e.preventDefault(); e.stopPropagation(); this.toggleFollow(); },
                'F': () => { e.preventDefault(); e.stopPropagation(); this.toggleFollow(); }
            };

            if (keyMap[e.key]) {
                e.preventDefault();
                e.stopImmediatePropagation(); // Prevent default browser actions
                keyMap[e.key]();
            }
        }

        handlePopState() {
            // This is primarily for when navigating back from a profile page after a follow action
            if (this.isActive && this.isReturningFromFollow) {
                console.log('Popstate detected, returning from follow action.');
                this.hideLoadingIndicator(); // This call is now safe due to the added null check
                this.isReturningFromFollow = false;
            }
        }

        disconnectObserver() {
            if (this.mutationObserver) {
                this.mutationObserver.disconnect();
                this.mutationObserver = null;
                console.log('MutationObserver disconnected.');
            }
        }
    }

    // Initialize the TikTokFeed instance once the DOM is ready
    function onReady() {
        // Clean up any existing instance to prevent duplicates if script runs multiple times
        if (window.tikTokFeedInstance) {
            console.log('Existing TikTok Feed instance found, cleaning up...');
            // Remove all event listeners associated with the old instance
            if (window.tikTokFeedInstance.boundHandleWheel) {
                document.removeEventListener('wheel', window.tikTokFeedInstance.boundHandleWheel, { passive: false, capture: true });
                document.removeEventListener('keydown', window.tikTokFeedInstance.boundHandleKeydown, { capture: true });
                window.removeEventListener('popstate', window.tikTokFeedInstance.boundHandlePopState);
            }
            // Ensure the feed is exited cleanly if it was active
            if (window.tikTokFeedInstance.isActive) {
                window.tikTokFeedInstance.exit();
            }
            // Pause any currently displayed media from the old instance
            if (window.tikTokFeedInstance.activeDisplayedMediaElement && window.tikTokFeedInstance.activeDisplayedMediaElement.tagName === 'VIDEO') {
                window.tikTokFeedInstance.activeDisplayedMediaElement.pause();
                window.tikTokFeedInstance.activeDisplayedMediaElement.muted = true;
            }

            window.tikTokFeedInstance.disconnectObserver(); // Disconnect any MutationObserver
            window.tikTokFeedInstance = null; // Dereference the old instance
            console.log('Previous TikTok Feed instance cleaned up.');
        }

        // Create and initialize a new instance
        window.tikTokFeedInstance = new TikTokFeed();
        window.tikTokFeedInstance.init();
    }

    // Run onReady when the document is ready
    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        onReady();
    } else {
        document.addEventListener('DOMContentLoaded', onReady);
    }
})();