Instagram Sample Bot (Adaptive Scroll, Sample & Like)

Automates Instagram scrolling: fluid in foreground, jumpy in background. Detects and filters sample offers by popular beauty brands, and auto-likes relevant posts. Includes customizable settings and improved UI. Now with in-app Help and Update options in settings, improved modal visibility, fluid UI panel animations, intelligent loading detection, and draggable UI confined to viewport.

// ==UserScript==
// @name         Instagram Sample Bot (Adaptive Scroll, Sample & Like)
// @namespace    http://tampermonkey.net/
// @version      1.43
// @description  Automates Instagram scrolling: fluid in foreground, jumpy in background. Detects and filters sample offers by popular beauty brands, and auto-likes relevant posts. Includes customizable settings and improved UI. Now with in-app Help and Update options in settings, improved modal visibility, fluid UI panel animations, intelligent loading detection, and draggable UI confined to viewport.
// @author       dprits419
// @match        https://www.instagram.com/*
// @run-at       document-idle
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // IMPORTANT: Make sure this version matches the @version in the header!
    const SCRIPT_VERSION = "1.43";

    // --- IMPORTANT: CONFIGURE THESE URLs IF YOU HOST YOUR SCRIPT ---
    // This is the URL where your raw .user.js script is hosted.
    // When the "Update Userscript" button is clicked, it opens this URL.
    // Tampermonkey will detect the .user.js file and prompt for update if a newer version is available.
    const SCRIPT_INSTALL_URL = 'https://update.greasyfork.org/scripts/538055/Instagram%20Sample%20Bot%20%28Adaptive%20Scroll%2C%20Sample%20%20Like%29.user.js'; // EXAMPLE: Replace with your actual GitHub Gist raw URL or Greasy Fork URL

    // This is the URL for a help page or README for your script.
    // NOTE: This URL is now primarily for reference, as help is shown in-app.
    const SCRIPT_HELP_URL = 'https://update.greasyfork.org/scripts/538055/Instagram%20Sample%20Bot%20%28Adaptive%20Scroll%2C%20Sample%20%20Like%29.user.js'; // EXAMPLE: Replace with your actual help/documentation URL
    // --- END CONFIGURATION ---

    // Processing parameters (these are not customizable via UI for now)
    const processPostsInterval = 2500; // How often to process posts (liking, sample detection)
    const endOfFeedConfirmDelay = 1500; // Milliseconds to confirm end of feed after hitting bottom

    console.log(`Instagram Bot script loaded! (Version ${SCRIPT_VERSION} - fixed issues, have fun ;))`);

    // Global settings object with defaults
    let settings = {
        pixelsPerFrame: 4,           // For fluid scrolling (foreground)
        jumpyScrollInterval: 1000,   // How often to jump in jumpy mode (background)
        enableLiking: true,          // Toggle auto-liking
        enableSampleDetection: true, // Toggle sample offer detection
        minConfidenceThreshold: 1.5, // Confidence required for sample detection
        acceptOnlyPopularBrands: false, // Filter sample offers by popular brands
    };


    // Global state for bot activity
    let scrolling = false;

    // References to the two types of scrolling mechanisms
    let fluidAnimationFrameId = null; // For requestAnimationFrame
    let jumpyScrollIntervalId = null; // For setInterval (background)
    let processPostsIntervalId = null; // Separate interval for processing posts

    // Track last known scroll positions for end-of-feed detection
    let lastScrollY = 0;
    let lastScrollHeight = 0;

    // --- Variables for Stuck/Loading Detection ---
    let stuckDetectionIntervalId = null; // For hard refresh
    let lastEffectiveScrollTime = Date.now(); // Timestamp of last *effective* scroll change (for hard stuck detection)
    const STUCK_TIMEOUT_MS = 30 * 1000; // 30 seconds without *any* scroll progress before full refresh
    const AUTO_START_FLAG = 'instagramBotAutoStartAfterReload'; // Flag for localStorage

    let noScrollProgressTimeout = null; // Timeout for initial detection of no scroll progress
    const NO_SCROLL_PROGRESS_WARN_DELAY = 3 * 1000; // 3 seconds without scroll progress to warn/slow down initially

    let isTemporarilyStuckLoading = false; // Flag to indicate if we're in the slow-down state

    let originalPixelsPerFrame = settings.pixelsPerFrame; // Store original for restoration
    let originalJumpyScrollInterval = settings.jumpyScrollInterval; // Store original for restoration
    const SLOW_SCROLL_FACTOR_FLUID = 0.5; // Reduce fluid speed to 50%
    const SLOW_SCROLL_FACTOR_JUMPY = 2.0; // Double jumpy interval (half speed)


    // Keywords for Sample Detection
    // General sample-related keywords (will be used with negative phrases for context)
    const sampleKeywords = ['free', 'sample', 'samples', 'offer', 'deal', 'giveaway', 'trial', 'complimentary', 'discount', 'coupon', 'win', 'contest', 'promo', 'gift'];

    // Phrases that strongly indicate an offer (higher confidence)
    const strongOfferPhrases = [
        'get your free', 'claim your free', 'win a free', 'enter to win',
        'get your sample', 'claim your sample', 'free product', 'free gift',
        'limited time offer', 'exclusive offer', 'sign up for free', 'redeem your', 'grab your'
    ];

    const sampleActionTexts = ['sign up', 'learn more', 'shop now', 'claim here', 'get sample', 'redeem', 'click here', 'get offer', 'apply now'];

    // Negative keywords/phrases to reduce false positives by penalizing confidence
    // These are very specific non-offer contexts for 'free' or 'sample'.
    const negativeSamplePhrases = [
        'cruelty-free', 'free from', 'gluten-free', 'sugar-free', 'dairy-free', 'ad-free',
        'sample size', 'sample of work', 'free to use', 'free to download', 'free trial',
        'sample chapter', 'sample lesson', 'sample video', 'sample audio', 'sample data',
        'sample image', 'sample text', 'free consultation', 'free estimate'
    ];

    // Keywords for Auto-Liking
    const cologneKeywords = ['cologne', 'fragrance', 'perfume', 'scent', 'eau de parfum', 'eau de toilette'];
    const beautyKeywords = [
        'skincare', 'makeup', 'cosmetics', 'beauty', 'haircare',
        'dior', 'chanel', 'sephora', 'ulta', 'fenty', 'kylie cosmetics', 'nars', 'mac cosmetics',
        'glossier', 'rare beauty', 'lancome', 'este lauder', 'clinique', 'shiseido',
        'hourglass', 'charlotte tilly', 'tatcha', 'kiehl\'s', 'cerave', 'la roche-posay',
        'olaplex', 'sol de janeiro', 'lip gloss', 'mascara', 'eyeshadow', 'blush', 'foundation', 'concealer',
        'serum', 'moisturizer', 'cleanser', 'sunscreen'
    ];

    // List of popular beauty brands for filtering
    const popularBeautyBrands = [
        'sephora', 'ulta', 'dior', 'chanel', 'fenty beauty', 'kylie cosmetics', 'nars', 'mac cosmetics',
        'glossier', 'rare beauty', 'lancome', 'este lauder', 'clinique', 'shiseido', 'hourglass',
        'charlotte tilly', 'tatcha', 'kiehl\'s', 'cerave', 'la roche-posay', 'olaplex', 'sol de janeiro',
        'anastasia beverly hills', 'too faced', 'urban decay', 'benefit cosmetics', 'tarte', 'it cosmetics',
        'fresh', 'sunday riley', 'drunk elephant', 'summer fridays', 'glow recipe', 'paula\'s choice',
        'the ordinary', 'skinfix', 'dr. jart+', 'biossance', 'supergoop', 'milk makeup', 'kosas', 'saie',
        'tower 28', 'innisfree', 'laneige', 'dr. brandt', 'peter thomas roth', 'first aid beauty', 'farmacy',
        'youth to the people', 'herbivore botanicals', 'origins', 'clarins', 'guerlain', 'sisley', 'la mer',
        'skinceuticals', 'obagi', 'elizabeth arden', 'aveeno', 'neutrogena', 'cetaphil', 'vichy', 'laroche-posay'
    ];

    let statusBarElement; // Reference to the status bar element

    // --- Settings Management ---

    function loadSettings() {
        try {
            const savedSettings = JSON.parse(localStorage.getItem('instagramBotSettings'));
            if (savedSettings) {
                // Merge saved settings with defaults to ensure new settings are added but old ones persist
                settings = { ...settings, ...savedSettings };
            }
            // Update original values based on loaded settings
            originalPixelsPerFrame = settings.pixelsPerFrame;
            originalJumpyScrollInterval = settings.jumpyScrollInterval;

            console.log("Settings loaded:", settings);
        } catch (e) {
            console.error("Error loading settings from localStorage:", e);
        }
    }

    function saveSettings() {
        try {
            localStorage.setItem('instagramBotSettings', JSON.stringify(settings));
            updateStatus("Settings saved!", 'var(--primary-color)');
            console.log("Settings saved:", settings);
            // Also update original values if settings are saved
            originalPixelsPerFrame = settings.pixelsPerFrame;
            originalJumpyScrollInterval = settings.jumpyScrollInterval;
        } catch (e) {
            console.error("Error saving settings to localStorage:", e);
            updateStatus("Failed to save settings!", 'var(--danger-color)');
        }
    }

    // --- Utility Functions ---

    // Helper to update the status bar
    function updateStatus(message, color = 'var(--text-color, #333)') {
        if (statusBarElement) {
            statusBarElement.innerText = message;
            statusBarElement.style.color = color;
        }
        console.log("STATUS: " + message);
    }

    // Helper for delays
    function delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    // Helper to detect if a post is sponsored
    function isPostSponsored(postElement) {
        const sponsoredIndicator = postElement.querySelector('span[aria-label="Sponsored"], div[aria-label="Sponsored"], [data-testid="sponsored-label"], span:-webkit-any(span, div)[tabindex="-1"][role="button"]');
        if (sponsoredIndicator && (sponsoredIndicator.innerText.toLowerCase().includes('sponsored') || sponsoredIndicator.getAttribute('aria-label') === 'Sponsored')) {
            return true;
        }
        return false;
    }

    // --- Liking Logic ---
    async function likePost(postElement) {
        // Check if already liked
        const unlikeButton = postElement.querySelector('svg[aria-label="Unlike"]')?.closest('button');
        if (unlikeButton) {
            console.log("[LikePost] Post already liked, skipping.");
            return false; // Already liked
        }

        let likeButton = null;

        // Attempt 1: Look for a button with aria-label="Like" directly
        likeButton = postElement.querySelector('button[aria-label="Like"]');
        if (likeButton) {
            console.log("[LikePost] Found like button using button[aria-label='Like'].");
        }

        // Attempt 2: Look for a div with role="button" and aria-label="Like"
        if (!likeButton) {
            likeButton = postElement.querySelector('div[role="button"][aria-label="Like"]');
            if (likeButton) {
                console.log("[LikePost] Found like button using div[role='button'][aria-label='Like'].");
            }
        }

        // Attempt 3: Look for an element with role="img" and aria-label="Like" (as per user feedback)
        if (!likeButton) {
            likeButton = postElement.querySelector('[role="img"][aria-label="Like"]');
            if (likeButton) {
                // If it's an image role, we need to find its clickable parent (usually a button or div[role="button"])
                const clickableParent = likeButton.closest('button, div[role="button"]');
                if (clickableParent) {
                    likeButton = clickableParent;
                    console.log("[LikePost] Found like button using role='img' and aria-label, then its clickable parent.");
                } else {
                    // If no clickable parent, the image itself might be clickable (less common but possible)
                    console.log("[LikePost] Found like button using role='img' and aria-label, but no explicit clickable parent. Clicking image directly.");
                }
            }
        }

        // Attempt 4: Look for a button containing an SVG with aria-label="Like"
        if (!likeButton) {
            likeButton = postElement.querySelector('svg[aria-label="Like"]')?.closest('button');
            if (likeButton) {
                console.log("[LikePost] Found like button using svg[aria-label='Like'] and closest('button').");
            }
        }

        // Attempt 5: Look for a button with a specific data-testid that might indicate a like button
        if (!likeButton) {
            likeButton = postElement.querySelector('button[data-testid="like-button"]');
            if (likeButton) {
                console.log("[LikePost] Found like button using button[data-testid='like-button'].");
            }
        }

        // Attempt 6: More robust: Find the action bar (usually a div/footer containing interaction buttons)
        // Then look for the like button within that specific bar.
        if (!likeButton) {
            const actionBarSelectors = [
                'div[role="group"][aria-label*="actions"]',
                'footer',
                'div[role="group"]',
                'div[class*="Post__actions"]',
                'div[class*="Engagement__actions"]',
                'div[class*="Reactions__container"]',
                'div:has(button[aria-label="Like"], button[aria-label="Comment"], button[aria-label="Share"])',
                'div:has(div[role="button"][aria-label="Like"], div[role="button"][aria-label="Comment"], div[role="button"][aria-label="Share"])',
                'div:has([role="img"][aria-label="Like"], [role="img"][aria-label="Comment"], [role="img"][aria-label="Share"])', // New: check for image roles in action bar
                'div[style*="flex-direction: row"]'
            ];

            let actionBar = null;
            for (const selector of actionBarSelectors) {
                actionBar = postElement.querySelector(selector);
                if (actionBar) {
                    console.log(`[LikePost] Found potential action bar using selector: ${selector}`);
                    break;
                }
            }

            if (actionBar) {
                // Within the action bar, try to find the like button using its common attributes
                likeButton = actionBar.querySelector('button[aria-label="Like"]') ||
                             actionBar.querySelector('div[role="button"][aria-label="Like"]') ||
                             actionBar.querySelector('svg[aria-label="Like"]')?.closest('button') ||
                             actionBar.querySelector('button[data-testid="like-button"]') ||
                             actionBar.querySelector('[role="img"][aria-label="Like"]')?.closest('button, div[role="button"]'); // New: find image role within action bar and its parent
                if (likeButton) {
                    console.log("[LikePost] Found like button within action bar.");
                } else {
                    console.log("[LikePost] Like button not found within the identified action bar.");
                }
            } else {
                console.log("[LikePost] Action bar not found for this post after all attempts.");
            }
        }


        if (likeButton) {
            console.log("[LikePost] Final check: Like button found, attempting to click.");
            likeButton.click();
            return true; // Successfully clicked like button
        }
        console.log("[LikePost] Like button not found for this post after all attempts.");
        return false; // Like button not found
    }

    // --- Scrolling Mode Management ---

    // Starts continuous, fluid scrolling using requestAnimationFrame
    function startFluidScrolling() {
        if (fluidAnimationFrameId) return; // Already fluid scrolling

        // Ensure jumpy scrolling is stopped
        if (jumpyScrollIntervalId) {
            clearInterval(jumpyScrollIntervalId);
            jumpyScrollIntervalId = null;
        }

        function animateScroll() {
            if (!scrolling) return; // Stop if scrolling is cancelled

            const currentScrollY = window.scrollY;
            const currentScrollHeight = document.documentElement.scrollHeight;

            if (currentScrollY === lastScrollY && currentScrollHeight === lastScrollHeight) {
                // No scroll progress
                if (!noScrollProgressTimeout) {
                    // Start a timeout to detect prolonged lack of scroll progress
                    noScrollProgressTimeout = setTimeout(() => {
                        if (window.scrollY === lastScrollY && document.documentElement.scrollHeight === lastScrollHeight) {
                            // Still no scroll progress after initial delay, slow down
                            isTemporarilyStuckLoading = true;
                            settings.pixelsPerFrame = Math.max(1, Math.round(originalPixelsPerFrame * SLOW_SCROLL_FACTOR_FLUID));
                            updateStatus(`Slowing for loading (${settings.pixelsPerFrame}px/frame)...`, 'orange');
                        }
                        noScrollProgressTimeout = null; // Clear this timeout regardless if it triggered slow down or not
                    }, NO_SCROLL_PROGRESS_WARN_DELAY);
                }
                // If we are in a slowed-down state, or just detected no scroll progress, don't scroll further yet.
                // We wait for scrollHeight to change or the stuck detection to trigger a refresh.
                if (isTemporarilyStuckLoading) {
                     fluidAnimationFrameId = requestAnimationFrame(animateScroll); // Keep animation loop alive but don't scroll
                     return;
                }
            } else {
                // Scroll progress made
                clearTimeout(noScrollProgressTimeout);
                noScrollProgressTimeout = null;

                // If we were temporarily stuck, reset to original speed
                if (isTemporarilyStuckLoading) {
                    isTemporarilyStuckLoading = false;
                    settings.pixelsPerFrame = originalPixelsPerFrame;
                    updateStatus("Resuming normal scroll speed...", 'var(--primary-color, #4CAF50)');
                }
            }

            // Only scroll if not in a temporarily stuck loading state
            if (!isTemporarilyStuckLoading) {
                window.scrollBy(0, settings.pixelsPerFrame); // Use current (potentially modified) setting
            }


            lastScrollY = currentScrollY;
            lastScrollHeight = currentScrollHeight;

            fluidAnimationFrameId = requestAnimationFrame(animateScroll);
        }
        fluidAnimationFrameId = requestAnimationFrame(animateScroll);
        updateStatus("Scrolling feed fluidly...", 'var(--primary-color, #4CAF50)');
    }

    // Stops fluid scrolling
    function stopFluidScrolling() {
        if (fluidAnimationFrameId) {
            cancelAnimationFrame(fluidAnimationFrameId);
            fluidAnimationFrameId = null;
        }
        clearTimeout(noScrollProgressTimeout); // Clear loading timeout if stopping
        noScrollProgressTimeout = null;
        if (isTemporarilyStuckLoading) { // Reset speed if it was slowed down
            settings.pixelsPerFrame = originalPixelsPerFrame;
            isTemporarilyStuckLoading = false;
        }
    }

    // Starts jumpy scrolling using setInterval (for background tabs)
    function startJumpyScrolling() {
        if (jumpyScrollIntervalId) return; // Already jumpy scrolling

        // Ensure fluid scrolling is stopped
        if (fluidAnimationFrameId) {
            cancelAnimationFrame(fluidAnimationFrameId);
            fluidAnimationFrameId = null;
        }

        jumpyScrollIntervalId = setInterval(() => {
            if (!scrolling) {
                clearInterval(jumpyScrollIntervalId);
                jumpyScrollIntervalId = null;
                return;
            }

            const currentScrollHeight = document.documentElement.scrollHeight;

            if (currentScrollHeight === lastScrollHeight) {
                // No scroll progress
                if (!noScrollProgressTimeout) {
                    noScrollProgressTimeout = setTimeout(() => {
                        if (document.documentElement.scrollHeight === lastScrollHeight) {
                            isTemporarilyStuckLoading = true;
                            settings.jumpyScrollInterval = Math.round(originalJumpyScrollInterval * SLOW_SCROLL_FACTOR_JUMPY);
                            updateStatus(`Slowing for loading (${settings.jumpyScrollInterval}ms interval)...`, 'orange');
                            // To apply new interval, restart the interval
                            clearInterval(jumpyScrollIntervalId);
                            jumpyScrollIntervalId = null;
                            startJumpyScrolling(); // Call itself to restart with new interval
                        }
                        noScrollProgressTimeout = null;
                    }, NO_SCROLL_PROGRESS_WARN_DELAY);
                }
                // If we are in a slowed-down state, or just detected no scroll progress, don't jump further yet.
                if (isTemporarilyStuckLoading) {
                    // Do nothing, the interval will just keep firing at the new (slower) rate, waiting for scrollHeight to change.
                    return;
                }
            } else {
                // Scroll progress made
                clearTimeout(noScrollProgressTimeout);
                noScrollProgressTimeout = null;

                // If we were temporarily stuck, reset to original speed
                if (isTemporarilyStuckLoading) {
                    isTemporarilyStuckLoading = false;
                    settings.jumpyScrollInterval = originalJumpyScrollInterval;
                    updateStatus("Resuming normal scroll speed...", 'var(--primary-color, #4CAF50)');
                    // To apply new interval, restart the interval
                    clearInterval(jumpyScrollIntervalId);
                    jumpyScrollIntervalId = null;
                    startJumpyScrolling(); // Call itself to restart with original interval
                }
            }

            // Only scroll if not in a temporarily stuck loading state
            if (!isTemporarilyStuckLoading) {
                window.scrollTo({ top: currentScrollHeight, behavior: 'instant' });
            }

            lastScrollHeight = currentScrollHeight;

        }, settings.jumpyScrollInterval); // Use current (potentially modified) setting
        updateStatus("Scrolling feed (jumpy in background)...", 'var(--primary-color, #4CAF50)');
    }

    // Stops jumpy scrolling
    function stopJumpyScrolling() {
        if (jumpyScrollIntervalId) {
            clearInterval(jumpyScrollIntervalId);
            jumpyScrollIntervalId = null;
        }
        clearTimeout(noScrollProgressTimeout); // Clear loading timeout if stopping
        noScrollProgressTimeout = null;
        if (isTemporarilyStuckLoading) { // Reset speed if it was slowed down
            settings.jumpyScrollInterval = originalJumpyScrollInterval;
            isTemporarilyStuckLoading = false;
        }
    }

    // --- Stuck Detection Logic (for full page refresh - ultimate fallback) ---
    let lastKnownScrollYForStuckDetection = 0;
    let lastKnownScrollHeightForStuckDetection = 0;

    function startStuckDetection() {
        if (stuckDetectionIntervalId) clearInterval(stuckDetectionIntervalId); // Clear any existing interval

        // Initialize last known scroll positions and time when detection starts
        lastKnownScrollYForStuckDetection = window.scrollY;
        lastKnownScrollHeightForStuckDetection = document.documentElement.scrollHeight;
        lastEffectiveScrollTime = Date.now(); // Reset time when detection starts

        stuckDetectionIntervalId = setInterval(() => {
            if (!scrolling) { // Only check if the bot is actively running
                clearInterval(stuckDetectionIntervalId);
                stuckDetectionIntervalId = null;
                return;
            }

            const currentScrollY = window.scrollY;
            const currentScrollHeight = document.documentElement.scrollHeight;

            // Check if *any* scroll progress has been made since the last check by THIS interval
            if (currentScrollY !== lastKnownScrollYForStuckDetection || currentScrollHeight !== lastKnownScrollHeightForStuckDetection) {
                lastEffectiveScrollTime = Date.now(); // Scroll position has changed, reset the "stuck" timer
            }

            // If no effective scroll progress for STUCK_TIMEOUT_MS, consider it truly stuck and reload
            if (Date.now() - lastEffectiveScrollTime > STUCK_TIMEOUT_MS) {
                console.warn(`[Instagram Bot] Bot appears truly stuck (no scroll progress for ${STUCK_TIMEOUT_MS / 1000}s). Initiating page refresh.`);
                updateStatus("Bot stuck! Refreshing page...", 'red'); // Use red for critical error/refresh
                stopBot(); // Stop cleanly before forcing a reload
                localStorage.setItem(AUTO_START_FLAG, 'true'); // Set flag to auto-start after reload
                location.reload(); // Reload the page
            }

            // Update for the next check by this interval
            lastKnownScrollYForStuckDetection = currentScrollY;
            lastKnownScrollHeightForStuckDetection = currentScrollHeight;

        }, 5000); // Check every 5 seconds for stuck state
    }

    // Main function to start the bot
    function startBot() {
        if (scrolling) return; // Bot is already active
        scrolling = true;

        // Ensure original speeds are correct before starting
        originalPixelsPerFrame = settings.pixelsPerFrame;
        originalJumpyScrollInterval = settings.jumpyScrollInterval;
        isTemporarilyStuckLoading = false; // Reset loading state

        // Start the post processing interval, which runs regardless of scroll mode
        if (!processPostsIntervalId) {
            processPostsIntervalId = setInterval(async () => {
                await processPostsBatch();
            }, processPostsInterval);
        }

        // Determine initial scrolling mode based on visibility
        if (document.visibilityState === 'visible') {
            startFluidScrolling();
        } else {
            startJumpyScrolling();
        }

        // Start monitoring for stuck state when the bot starts
        startStuckDetection();
    }

    // Main function to stop the bot
    function stopBot() {
        if (!scrolling) return; // Bot is already stopped
        scrolling = false;

        stopFluidScrolling(); // Stop fluid scrolling if active
        stopJumpyScrolling(); // Stop jumpy scrolling if active

        // Stop the post processing interval
        if (processPostsIntervalId) { // Check if it's active before clearing
            clearInterval(processPostsIntervalId);
            processPostsIntervalId = null;
        }

        // Stop the stuck detection interval
        if (stuckDetectionIntervalId) {
            clearInterval(stuckDetectionIntervalId);
            stuckDetectionIntervalId = null;
        }

        clearTimeout(noScrollProgressTimeout); // Clear any pending no-scroll checks
        noScrollProgressTimeout = null;
        updateStatus("Stopped.", 'var(--warning-color, #f44336)');
    }

    // --- Event Listener for Tab Visibility Change ---
    document.addEventListener('visibilitychange', () => {
        if (scrolling) { // Only change mode if bot is currently active
            // Stop current scrolling mode cleanly
            stopFluidScrolling();
            stopJumpyScrolling();

            // Restart scrolling in the appropriate new mode
            if (document.visibilityState === 'visible') {
                startFluidScrolling();
            } else {
                startJumpyScrolling();
            }
            // The stuck detection interval continues running, as it monitors overall scroll progress
        }
    });

    // Button styling templates for modals (moved to global scope)
    const modalButtonStyle = {
        padding: '12px 25px', borderRadius: '10px', border: 'none',
        fontWeight: 'bold', cursor: 'pointer', fontSize: '15px',
        transition: 'background-color 0.2s ease, transform 0.1s ease, box-shadow 0.2s ease',
        boxShadow: '0 4px 10px rgba(0, 0, 0, 0.3)',
    };
    const modalPrimaryButtonStyle = {
        backgroundColor: '#66B3FF', color: 'white',
        onmouseover: (btn) => { btn.style.backgroundColor = '#4DA8FF'; btn.style.boxShadow = '0 6px 15px rgba(77, 168, 255, 0.4)'; },
        onmouseout: (btn) => { btn.style.backgroundColor = '#66B3FF'; btn.style.boxShadow = '0 4px 10px rgba(0, 0, 0, 0.3)'; },
        onmousedown: (btn) => btn.style.transform = 'translateY(2px)',
        onmouseup: (btn) => btn.style.transform = 'translateY(0)',
    };
    const modalSecondaryButtonStyle = {
        border: '1px solid rgba(255,255,255,0.3)', backgroundColor: 'transparent', color: '#E0E0E0',
        onmouseover: (btn) => { btn.style.backgroundColor = 'rgba(255,255,255,0.1)'; btn.style.color = '#FFFFFF'; btn.style.boxShadow = '0 6px 15px rgba(255,255,255,0.15)'; },
        onmouseout: (btn) => { btn.style.backgroundColor = 'transparent'; btn.style.color = '#E0E0E0'; btn.style.boxShadow = '0 4px 10px rgba(0, 0, 0, 0.3)'; },
        onmousedown: (btn) => btn.style.transform = 'translateY(2px)',
        onmouseup: (btn) => btn.style.transform = 'translateY(0)',
    };

    // --- Custom Confirmation Modal Function ---
    function showCustomConfirmation(postElement, postSnippet, isSponsored, targetLinkHref) {
        console.log("Attempting to show custom confirmation modal.");
        return new Promise((resolve) => {
            const overlay = document.createElement('div');
            overlay.id = 'tm-custom-modal-overlay';
            Object.assign(overlay.style, {
                position: 'fixed', top: '0', left: '0', width: '100%', height: '100%',
                backgroundColor: 'rgba(0, 0, 0, 0.8)', // Darker overlay
                zIndex: '100000', display: 'flex',
                justifyContent: 'center', alignItems: 'center', opacity: '0',
                transition: 'opacity 0.3s ease-in-out',
            });

            const modal = document.createElement('div');
            modal.id = 'tm-custom-modal';
            Object.assign(modal.style, {
                backgroundColor: '#1A1A1A', // Darker modal background
                color: '#E0E0E0', // Lighter text
                borderRadius: '12px', padding: '30px', // Increased padding
                boxShadow: '0 8px 30px rgba(0, 0, 0, 0.7)', // Stronger shadow
                maxWidth: '480px', width: '90%', // Slightly wider
                fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif',
                display: 'flex', flexDirection: 'column', gap: '20px', // Increased gap
                transform: 'scale(0.9)', transition: 'transform 0.3s ease-in-out', // Initial state for animation
            });

            const header = document.createElement('div');
            Object.assign(header.style, { display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderBottom: '1px solid rgba(255,255,255,0.15)', paddingBottom: '15px', marginBottom: '15px' }); // Bolder border
            const title = document.createElement('h3');
            title.innerText = 'Potential Offer Found!';
            Object.assign(title.style, { margin: '0', fontSize: '22px', fontWeight: 'bold', color: '#66B3FF' }); // Bolder, slightly larger title
            const closeBtn = document.createElement('button');
            closeBtn.innerText = '✕';
            Object.assign(closeBtn.style, { background: 'none', border: 'none', color: '#E0E0E0', fontSize: '24px', cursor: 'pointer', padding: '5px', lineHeight: '1', transition: 'color 0.2s ease' }); // Larger close button
            closeBtn.onmouseover = () => closeBtn.style.color = '#FF4D4D'; // Red hover
            closeBtn.onmouseout = () => closeBtn.style.color = '#E0E0E0';
            closeBtn.onclick = () => { document.removeEventListener('keydown', handleEscape); document.body.removeChild(overlay); resolve(false); };
            header.appendChild(title); header.appendChild(closeBtn); modal.appendChild(header);

            const body = document.createElement('div');
            Object.assign(body.style, { fontSize: '16px', lineHeight: '1.7', color: '#D0D0D0' }); // Slightly larger font, lighter color
            const infoText = document.createElement('p');
            infoText.innerHTML = `An offer has been detected based on your criteria.` + (isSponsored ? ` <span style="font-weight: bold; color: #FFEB3B;">(Sponsored Post)</span>` : '');
            Object.assign(infoText.style, { margin: '0 0 15px 0' });
            const snippetHeader = document.createElement('p');
            snippetHeader.innerText = 'Post Snippet:';
            Object.assign(snippetHeader.style, { margin: '0', fontWeight: 'bold', color: '#B3B3FF' });
            const snippetContent = document.createElement('code');
            snippetContent.innerText = postSnippet;
            Object.assign(snippetContent.style, {
                display: 'block', backgroundColor: 'rgba(255,255,255,0.08)', borderRadius: '8px', padding: '15px', // Darker code block
                fontSize: '14px', whiteSpace: 'pre-wrap', wordBreak: 'break-word', maxHeight: '120px', // Increased height
                overflowY: 'auto', marginTop: '10px', marginBottom: '15px', color: '#A0A0A0'
            });
            body.appendChild(infoText); body.appendChild(snippetHeader); body.appendChild(snippetContent);

            if (targetLinkHref) {
                const linkP = document.createElement('p');
                linkP.innerText = 'Detected Link:';
                Object.assign(linkP.style, { margin: '0', fontWeight: 'bold', color: '#B3B3FF' });
                const linkElement = document.createElement('a');
                linkElement.href = targetLinkHref; linkElement.target = '_blank';
                linkElement.innerText = targetLinkHref.length > 60 ? targetLinkHref.substring(0, 57) + '...' : targetLinkHref;
                Object.assign(linkElement.style, { display: 'block', marginTop: '8px', color: '#66B3FF', wordBreak: 'break-all', textDecoration: 'underline', fontSize: '14px' });
                body.appendChild(linkP); body.appendChild(linkElement);
            } else {
                 const noLinkP = document.createElement('p');
                 noLinkP.innerText = "No direct link found. You'll need to manually inspect this post.";
                 Object.assign(noLinkP.style, { margin: '0', fontStyle: 'italic', color: '#808080' });
                 body.appendChild(noLinkP);
            }
            modal.appendChild(body);

            const footer = document.createElement('div');
            Object.assign(footer.style, { display: 'flex', justifyContent: 'flex-end', gap: '15px', paddingTop: '20px', borderTop: '1px solid rgba(255,255,255,0.15)', marginTop: '15px' }); // Increased gap and padding


            // "Open Link" button (if targetLinkHref exists)
            if (targetLinkHref) {
                const openLinkBtn = document.createElement('button');
                openLinkBtn.innerText = 'Open Link';
                Object.assign(openLinkBtn.style, modalButtonStyle, modalPrimaryButtonStyle);
                openLinkBtn.onmouseover = () => modalPrimaryButtonStyle.onmouseover(openLinkBtn);
                openLinkBtn.onmouseout = () => modalPrimaryButtonStyle.onmouseout(openLinkBtn);
                openLinkBtn.onmousedown = () => modalPrimaryButtonStyle.onmousedown(openLinkBtn);
                openLinkBtn.onmouseup = () => modalPrimaryButtonStyle.onmouseup(openLinkBtn);
                openLinkBtn.onclick = () => {
                    document.removeEventListener('keydown', handleEscape);
                    document.body.removeChild(overlay);
                    resolve('open_link'); // Resolve with 'open_link'
                };
                footer.appendChild(openLinkBtn);
            }

            // "Jump to Post" button (always available, but style changes)
            const jumpToPostBtn = document.createElement('button');
            jumpToPostBtn.innerText = 'Jump to Post';
            // If there's a direct link, Jump to Post becomes a secondary action. Otherwise, it's primary.
            const jumpBtnStyle = targetLinkHref ? modalSecondaryButtonStyle : modalPrimaryButtonStyle;
            Object.assign(jumpToPostBtn.style, modalButtonStyle, jumpBtnStyle);
            jumpToPostBtn.onmouseover = () => jumpBtnStyle.onmouseover(jumpToPostBtn);
            jumpToPostBtn.onmouseout = () => jumpBtnStyle.onmouseout(jumpToPostBtn);
            jumpToPostBtn.onmousedown = () => jumpBtnStyle.onmousedown(jumpToPostBtn);
            jumpToPostBtn.onmouseup = () => jumpBtnStyle.onmouseup(jumpToPostBtn);
            jumpToPostBtn.onclick = () => {
                document.removeEventListener('keydown', handleEscape);
                document.body.removeChild(overlay);
                if (postElement) {
                    postElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
                    console.log("Attempted to scroll to post.");
                }
                resolve('jump'); // Resolve with 'jump'
            };
            footer.appendChild(jumpToPostBtn);

            // "Dismiss & Resume" button (always available)
            const dismissBtn = document.createElement('button');
            dismissBtn.innerText = 'Dismiss & Resume';
            Object.assign(dismissBtn.style, modalButtonStyle, modalSecondaryButtonStyle);
            dismissBtn.onmouseover = () => modalSecondaryButtonStyle.onmouseover(dismissBtn);
            dismissBtn.onmouseout = () => modalSecondaryButtonStyle.onmouseout(dismissBtn);
            dismissBtn.onmousedown = () => modalSecondaryButtonStyle.onmousedown(dismissBtn);
            dismissBtn.onmouseup = () => modalSecondaryButtonStyle.onmouseup(dismissBtn);
            dismissBtn.onclick = () => { document.removeEventListener('keydown', handleEscape); document.body.removeChild(overlay); resolve('dismiss'); }; // Resolve with 'dismiss'
            footer.appendChild(dismissBtn);

            modal.appendChild(footer); overlay.appendChild(modal); document.body.appendChild(overlay);

            // Animate modal entry
            setTimeout(() => { overlay.style.opacity = '1'; modal.style.transform = 'scale(1)'; }, 10);

            const handleEscape = (e) => {
                if (e.key === 'Escape') {
                    document.removeEventListener('keydown', handleEscape);
                    document.body.removeChild(overlay);
                    resolve('dismiss'); // Escape key also dismisses and resumes
                }
            };
            document.addEventListener('keydown', handleEscape);
        });
    }

    // --- Help Modal Function ---
    async function showHelpModal() {
        return new Promise((resolve) => {
            const overlay = document.createElement('div');
            overlay.id = 'tm-help-overlay';
            Object.assign(overlay.style, {
                position: 'fixed', top: '0', left: '0', width: '100%', height: '100%',
                backgroundColor: 'rgba(0, 0, 0, 0.8)', zIndex: '100001', display: 'flex',
                justifyContent: 'center', alignItems: 'center', opacity: '0', transition: 'opacity 0.3s ease-in-out'
            });

            const modal = document.createElement('div');
            modal.id = 'tm-help-modal';
            Object.assign(modal.style, {
                backgroundColor: '#1A1A1A', color: '#E0E0E0', borderRadius: '12px', padding: '30px',
                boxShadow: '0 8px 30px rgba(0, 0, 0, 0.7)', maxWidth: '480px', width: '90%',
                fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif',
                display: 'flex', flexDirection: 'column', gap: '20px', transform: 'scale(0.9)', transition: 'transform 0.3s ease-in-out'
            });

            const header = document.createElement('div');
            Object.assign(header.style, { display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderBottom: '1px solid rgba(255,255,255,0.15)', paddingBottom: '15px', marginBottom: '15px' });
            const title = document.createElement('h3');
            title.innerText = 'Bot Help & Troubleshooting';
            Object.assign(title.style, { margin: '0', fontSize: '22px', fontWeight: 'bold', color: '#66B3FF' });
            const closeBtn = document.createElement('button');
            closeBtn.innerText = '✕';
            Object.assign(closeBtn.style, { background: 'none', border: 'none', color: '#E0E0E0', fontSize: '24px', cursor: 'pointer', padding: '5px', lineHeight: '1', transition: 'color 0.2s ease' });
            closeBtn.onmouseover = () => closeBtn.style.color = '#FF4D4D';
            closeBtn.onmouseout = () => closeBtn.style.color = '#E0E0E0';
            closeBtn.onclick = () => { document.removeEventListener('keydown', handleEscape); document.body.removeChild(overlay); resolve(false); };
            header.appendChild(title); header.appendChild(closeBtn); modal.appendChild(header);

            const content = document.createElement('div');
            Object.assign(content.style, { fontSize: '16px', lineHeight: '1.7', color: '#D0D0D0', maxHeight: '300px', overflowY: 'auto' });
            content.innerHTML = `
                <p>If the Instagram Bot isn't working as expected, try these steps:</p>
                <ul>
                    <li><strong>Ensure Tampermonkey is active:</strong> Check your browser's Tampermonkey extension icon; it should be enabled.</li>
                    <li><strong>Script Enabled:</strong> In the Tampermonkey dashboard, make sure 'Instagram Sample Bot' is toggled ON.</li>
                    <li><strong>Disable Ad Blockers:</strong> Ad blockers (e.g., uBlock Origin, AdBlock Plus) or privacy extensions can interfere. Try disabling them for instagram.com.</li>
                    <li><strong>Refresh Page:</strong> A simple page refresh (F5 or Ctrl+R / Cmd+R) can often resolve minor issues.</li>
                    <li><strong>Restart Bot:</strong> If the bot stops, try clicking the 'Start Bot' button in the UI.</li>
                    <li><strong>Check Console for Errors:</strong> Open your browser's developer tools (F12 or Ctrl+Shift+I / Cmd+Option+I), go to the 'Console' tab, and look for any red error messages. Report them if you need further help.</li>
                    <li><strong>Update Script:</strong> Click 'Update Userscript' in the settings to check for the latest version.</li>
                    <li><strong>Clear Cache/Cookies:</strong> As a last resort, clearing browser cache and cookies for instagram.com can sometimes fix persistent issues (note: this will log you out).</li>
                </ul>
                <p>If you still face problems, please contact the script developer with details!</p>
            `;
            modal.appendChild(content);

            const footer = document.createElement('div');
            Object.assign(footer.style, { display: 'flex', justifyContent: 'flex-end', gap: '15px', paddingTop: '20px', borderTop: '1px solid rgba(255,255,255,0.15)', marginTop: '15px' });

            const closeButton = document.createElement('button');
            closeButton.innerText = 'Close';
            Object.assign(closeButton.style, {
                padding: '12px 25px', borderRadius: '10px', border: '1px solid rgba(255,255,255,0.3)',
                backgroundColor: 'transparent', color: '#E0E0E0', fontWeight: 'bold', cursor: 'pointer',
                transition: 'background-color 0.2s ease, color 0.2s ease, transform 0.1s ease, box-shadow 0.2s ease',
                boxShadow: '0 4px 10px rgba(0, 0, 0, 0.3)',
            });
            closeButton.onmouseover = () => { closeButton.style.backgroundColor = 'rgba(255,255,255,0.1)'; closeButton.style.color = '#FFFFFF'; closeButton.style.boxShadow = '0 6px 15px rgba(255,255,255,0.15)'; };
            closeButton.onmouseout = () => { closeButton.style.backgroundColor = 'transparent'; closeButton.style.color = '#E0E0E0'; closeButton.style.boxShadow = '0 4px 10px rgba(0, 0, 0, 0.3)'; };
            closeButton.onmousedown = () => closeButton.style.transform = 'translateY(2px)';
            closeButton.onmouseup = () => closeButton.style.transform = 'translateY(0)';
            closeButton.onclick = () => { document.removeEventListener('keydown', handleEscape); document.body.removeChild(overlay); resolve(false); };
            footer.appendChild(closeButton);

            modal.appendChild(footer); overlay.appendChild(modal); document.body.appendChild(overlay);

            setTimeout(() => { overlay.style.opacity = '1'; modal.style.transform = 'scale(1)'; }, 10);

            const handleEscape = (e) => {
                if (e.key === 'Escape') {
                    document.removeEventListener('keydown', handleEscape);
                    document.body.removeChild(overlay);
                    resolve(false);
                }
            };
            document.addEventListener('keydown', handleEscape);
        });
    }


    // --- Settings Modal Function ---
    async function showSettingsModal() {
        return new Promise((resolve) => {
            const overlay = document.createElement('div');
            overlay.id = 'tm-settings-overlay';
            Object.assign(overlay.style, {
                position: 'fixed', top: '0', left: '0', width: '100%', height: '100%',
                backgroundColor: 'rgba(0, 0, 0, 0.8)', zIndex: '100001', display: 'flex',
                justifyContent: 'center', alignItems: 'center', opacity: '0', transition: 'opacity 0.3s ease-in-out'
            });

            const modal = document.createElement('div');
            modal.id = 'tm-settings-modal';
            Object.assign(modal.style, {
                backgroundColor: '#1A1A1A', color: '#E0E0E0', borderRadius: '12px', padding: '30px',
                boxShadow: '0 8px 30px rgba(0, 0, 0, 0.7)', maxWidth: '480px', width: '90%',
                fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif',
                display: 'flex', flexDirection: 'column', gap: '20px', transform: 'scale(0.9)', transition: 'transform 0.3s ease-in-out'
            });

            const header = document.createElement('div');
            Object.assign(header.style, { display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderBottom: '1px solid rgba(255,255,255,0.15)', paddingBottom: '15px', marginBottom: '15px' });
            const title = document.createElement('h3');
            title.innerText = 'Bot Settings';
            Object.assign(title.style, { margin: '0', fontSize: '22px', fontWeight: 'bold', color: '#66B3FF' });
            const closeBtn = document.createElement('button');
            closeBtn.innerText = '✕';
            Object.assign(closeBtn.style, { background: 'none', border: 'none', color: '#E0E0E0', fontSize: '24px', cursor: 'pointer', padding: '5px', lineHeight: '1', transition: 'color 0.2s ease' });
            closeBtn.onmouseover = () => closeBtn.style.color = '#FF4D4D';
            closeBtn.onmouseout = () => closeBtn.style.color = '#E0E0E0';
            closeBtn.onclick = () => { document.removeEventListener('keydown', handleEscape); document.body.removeChild(overlay); resolve(false); };
            header.appendChild(title); header.appendChild(closeBtn); modal.appendChild(header);

            const settingsForm = document.createElement('div');
            Object.assign(settingsForm.style, { display: 'flex', flexDirection: 'column', gap: '15px' }); // Increased gap

            // Helper to create a setting row
            const createSettingRow = (labelText, inputElement) => {
                const row = document.createElement('div');
                Object.assign(row.style, { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '5px 0' }); // Added padding
                const label = document.createElement('label');
                label.innerText = labelText;
                Object.assign(label.style, { flexShrink: '0', marginRight: '15px', fontSize: '16px', color: '#D0D0D0' }); // Larger font, lighter color
                row.appendChild(label);
                row.appendChild(inputElement);
                return row;
            };

            // Input field styling for settings
            const inputStyle = {
                width: '90px', padding: '10px', borderRadius: '8px', border: '1px solid #555',
                backgroundColor: '#333', color: '#F0F0F0', fontSize: '15px',
                boxShadow: 'inset 0 1px 3px rgba(0,0,0,0.3)', // Inner shadow
            };

            // Checkbox styling for settings
            const checkboxStyle = {
                width: '24px', height: '24px', cursor: 'pointer',
                accentColor: '#66B3FF', // Highlight color for checkbox
            };

            // Scrolling Speed (Fluid)
            const fluidSpeedInput = document.createElement('input');
            fluidSpeedInput.type = 'number';
            fluidSpeedInput.min = '1'; fluidSpeedInput.max = '20'; fluidSpeedInput.step = '1';
            fluidSpeedInput.value = settings.pixelsPerFrame;
            Object.assign(fluidSpeedInput.style, inputStyle);
            settingsForm.appendChild(createSettingRow('Fluid Scroll Speed (px/frame):', fluidSpeedInput));

            // Scrolling Interval (Jumpy)
            const jumpyIntervalInput = document.createElement('input');
            jumpyIntervalInput.type = 'number';
            jumpyIntervalInput.min = '100'; jumpyIntervalInput.max = '5000'; jumpyIntervalInput.step = '100';
            jumpyIntervalInput.value = settings.jumpyScrollInterval;
            Object.assign(jumpyIntervalInput.style, inputStyle);
            settingsForm.appendChild(createSettingRow('Jumpy Scroll Interval (ms):', jumpyIntervalInput));

            // Auto-Liking Toggle
            const autoLikeToggle = document.createElement('input');
            autoLikeToggle.type = 'checkbox';
            autoLikeToggle.checked = settings.enableLiking;
            Object.assign(autoLikeToggle.style, checkboxStyle);
            settingsForm.appendChild(createSettingRow('Enable Specific Auto-Liking:', autoLikeToggle));

            // Sample Detection Toggle
            const sampleDetectToggle = document.createElement('input');
            sampleDetectToggle.type = 'checkbox';
            sampleDetectToggle.checked = settings.enableSampleDetection;
            Object.assign(sampleDetectToggle.style, checkboxStyle);
            settingsForm.appendChild(createSettingRow('Enable Sample Detection:', sampleDetectToggle));

            // Confidence Threshold for Sample Detection
            const confidenceThresholdInput = document.createElement('input');
            confidenceThresholdInput.type = 'number';
            confidenceThresholdInput.min = '0.0'; confidenceThresholdInput.max = '5.0'; confidenceThresholdInput.step = '0.1'; // Max increased to accommodate new scoring
            confidenceThresholdInput.value = settings.minConfidenceThreshold;
            Object.assign(confidenceThresholdInput.style, inputStyle);
            settingsForm.appendChild(createSettingRow('Sample Confidence Threshold:', confidenceThresholdInput));

            // Accept Only Popular Brands Toggle
            const popularBrandsToggle = document.createElement('input');
            popularBrandsToggle.type = 'checkbox';
            popularBrandsToggle.checked = settings.acceptOnlyPopularBrands;
            Object.assign(popularBrandsToggle.style, checkboxStyle);
            settingsForm.appendChild(createSettingRow('Only Popular Beauty Brands:', popularBrandsToggle));


            modal.appendChild(settingsForm);

            const footer = document.createElement('div');
            Object.assign(footer.style, { display: 'flex', justifyContent: 'flex-end', gap: '15px', paddingTop: '20px', borderTop: '1px solid rgba(255,255,255,0.15)', marginTop: '15px', flexWrap: 'wrap' }); // Added flexWrap

            // Buttons in settings modal use the same modal button styles
            const helpBtn = document.createElement('button');
            helpBtn.innerText = 'Help';
            Object.assign(helpBtn.style, modalButtonStyle, {
                border: '1px solid rgba(102, 179, 255, 0.5)',
                backgroundColor: 'transparent', color: '#66B3FF',
            });
            helpBtn.onmouseover = () => { helpBtn.style.backgroundColor = 'rgba(102, 179, 255, 0.1)'; helpBtn.style.boxShadow = '0 6px 15px rgba(102, 179, 255, 0.2)'; };
            helpBtn.onmouseout = () => { helpBtn.style.backgroundColor = 'transparent'; helpBtn.style.boxShadow = '0 4px 10px rgba(0, 0, 0, 0.3)'; };
            helpBtn.onmousedown = () => helpBtn.style.transform = 'translateY(2px)';
            helpBtn.onmouseup = () => helpBtn.style.transform = 'translateY(0)';
            helpBtn.onclick = async () => {
                console.log("Help button clicked from settings modal.");
                try {
                    await showHelpModal();
                } catch (e) {
                    console.error("Error executing showHelpModal from settings:", e);
                    updateStatus("Error opening help!", 'red');
                }
            };
            footer.appendChild(helpBtn);

            const updateBtn = document.createElement('button');
            updateBtn.innerText = 'Update Userscript';
            Object.assign(updateBtn.style, modalButtonStyle, {
                border: '1px solid rgba(144, 238, 144, 0.5)',
                backgroundColor: 'transparent', color: '#90EE90',
            });
            updateBtn.onmouseover = () => { updateBtn.style.backgroundColor = 'rgba(144, 238, 144, 0.1)'; updateBtn.style.boxShadow = '0 6px 15px rgba(144, 238, 144, 0.2)'; };
            updateBtn.onmouseout = () => { updateBtn.style.backgroundColor = 'transparent'; updateBtn.style.boxShadow = '0 4px 10px rgba(0, 0, 0, 0.3)'; };
            updateBtn.onmousedown = () => updateBtn.style.transform = 'translateY(2px)';
            updateBtn.onmouseup = () => updateBtn.style.transform = 'translateY(0)';
            updateBtn.onclick = () => {
                window.open(SCRIPT_INSTALL_URL, '_blank');
            };
            footer.appendChild(updateBtn);


            const saveBtn = document.createElement('button');
            saveBtn.innerText = 'Save Settings';
            Object.assign(saveBtn.style, modalButtonStyle, modalPrimaryButtonStyle);
            saveBtn.onmouseover = () => modalPrimaryButtonStyle.onmouseover(saveBtn);
            saveBtn.onmouseout = () => modalPrimaryButtonStyle.onmouseout(saveBtn);
            saveBtn.onmousedown = () => modalPrimaryButtonStyle.onmousedown(saveBtn);
            saveBtn.onmouseup = () => modalPrimaryButtonStyle.onmouseup(saveBtn);
            saveBtn.onclick = () => {
                settings.pixelsPerFrame = parseInt(fluidSpeedInput.value);
                settings.jumpyScrollInterval = parseInt(jumpyIntervalInput.value);
                settings.enableLiking = autoLikeToggle.checked;
                settings.enableSampleDetection = sampleDetectToggle.checked;
                settings.minConfidenceThreshold = parseFloat(confidenceThresholdInput.value);
                settings.acceptOnlyPopularBrands = popularBrandsToggle.checked;
                saveSettings();
                document.removeEventListener('keydown', handleEscape);
                document.body.removeChild(overlay);
                resolve(true); // Indicate settings were saved
            };
            footer.appendChild(saveBtn);

            const cancelBtn = document.createElement('button');
            cancelBtn.innerText = 'Cancel';
            Object.assign(cancelBtn.style, modalButtonStyle, modalSecondaryButtonStyle);
            cancelBtn.onmouseover = () => modalSecondaryButtonStyle.onmouseover(cancelBtn);
            cancelBtn.onmouseout = () => modalSecondaryButtonStyle.onmouseout(cancelBtn);
            cancelBtn.onmousedown = () => modalSecondaryButtonStyle.onmousedown(cancelBtn);
            cancelBtn.onmouseup = () => modalSecondaryButtonStyle.onmouseup(cancelBtn);
            cancelBtn.onclick = () => { document.removeEventListener('keydown', handleEscape); document.body.removeChild(overlay); resolve(false); };
            footer.appendChild(cancelBtn);

            modal.appendChild(footer); overlay.appendChild(modal); document.body.appendChild(overlay);

            setTimeout(() => { overlay.style.opacity = '1'; modal.style.transform = 'scale(1)'; }, 10);

            const handleEscape = (e) => {
                if (e.key === 'Escape') {
                    document.removeEventListener('keydown', handleEscape);
                    document.body.removeChild(overlay);
                    resolve(false);
                }
            };
            document.addEventListener('keydown', handleEscape);
        });
    }


    async function processPostsBatch() {
        // IMPORTANT: Add this check at the very beginning to ensure bot stops all processing immediately
        if (!scrolling) {
            console.log("[processPostsBatch] Bot is stopped, exiting batch processing.");
            return;
        }

        const posts = document.querySelectorAll('article');

        for (const post of posts) {
            const postText = post.innerText.toLowerCase();

            // --- LIKING LOGIC ---
            // Only process liking if the bot is actively scrolling (and thus processing)
            if (scrolling && settings.enableLiking && !post.dataset.likedChecked) {
                post.dataset.likedChecked = 'true'; // Mark as checked to prevent re-processing

                const shouldLike = cologneKeywords.some(kw => postText.includes(kw)) ||
                                   beautyKeywords.some(kw => postText.includes(kw));

                if (shouldLike) {
                    console.log(`[Liking Check] Post contains relevant keywords for liking. Post snippet: ${postText.substring(0, 100)}...`);
                    const likedSuccessfully = await likePost(post);
                    if (likedSuccessfully) {
                        updateStatus("Liked a post!", 'var(--primary-color)');
                        await delay(1000 + Math.random() * 1000); // Wait 1-2 seconds after liking
                    } else {
                        console.log("[Liking Check] Failed to like post or it was already liked.");
                    }
                } else {
                    console.log("[Liking Check] Post does not contain relevant keywords for liking.");
                }
            }

            // --- SAMPLE DETECTION LOGIC ---
            // Ensure bot is still active before proceeding with sample detection
            if (!scrolling || !settings.enableSampleDetection || post.dataset.sampleChecked) {
                continue;
            }

            // Filter by popular brands if setting is enabled
            if (settings.acceptOnlyPopularBrands) {
                const foundPopularBrand = popularBeautyBrands.some(brand => postText.includes(brand.toLowerCase()));
                if (!foundPopularBrand) {
                    continue; // Skip this post if it's not from a popular brand
                }
            }

            let confidence = 0;
            let targetLinkHref = null; // Changed to store href directly

            const isSponsored = isPostSponsored(post);
            let hasAnySampleKeyword = false;
            let hasNegativePhrase = false;


            // 1. Boost for strong offer phrases (highest priority)
            for (const phrase of strongOfferPhrases) {
                if (postText.includes(phrase)) {
                    confidence += 3.0; // High boost for direct offer phrases
                }
            }

            // 2. Boost for action texts (strong indicator of an interactive offer)
            const buttonsAndLinks = post.querySelectorAll('a, button');
            for (const element of buttonsAndLinks) {
                const elementText = element.innerText.toLowerCase();
                if (sampleActionTexts.some(action => elementText.includes(action))) {
                    if (element.tagName === 'A' && element.href && element.href !== '#' && !element.href.startsWith('javascript:')) {
                        // Prioritize action-associated links if found, or if no direct text link yet
                        if (!targetLinkHref || element.href.includes('bit.ly') || element.href.includes('tinyurl')) { // Prioritize short links or if no other link
                            targetLinkHref = element.href;
                        }
                    }
                    confidence += 2.0; // Strong boost for action buttons/links
                    break; // Only need to find one action text
                }
            }

            // NEW: Extract direct links from post text (more robust URL detection)
            const urlRegex = /(https?:\/\/[^\s]+)/g; // Basic URL regex
            const matches = postText.match(urlRegex);
            if (matches && matches.length > 0) {
                // If we found a direct URL in the text, use it if targetLinkHref isn't already set by an action button
                if (!targetLinkHref) {
                    targetLinkHref = matches[0]; // Take the first URL found
                }
                confidence += 1.0; // Add confidence for finding a direct link
            }


            // 3. Boost for sponsored posts
            if (isSponsored) {
                confidence += 1.0; // Bonus for sponsored posts
            }

            // Identify presence of any generic sample keyword
            for (const keyword of sampleKeywords) {
                if (postText.includes(keyword)) {
                    hasAnySampleKeyword = true;
                    break;
                }
            }

            // Identify presence of any negative sample phrase
            for (const negPhrase of negativeSamplePhrases) {
                if (postText.includes(negPhrase)) {
                    hasNegativePhrase = true;
                    break;
                }
            }

            // 4. Conditional boost/penalty for general sample keywords based on negative context
            if (hasAnySampleKeyword) {
                if (!hasNegativePhrase) {
                    confidence += 1.5; // Significant boost if general keyword is clean
                } else {
                    confidence -= 2.5; // Strong penalty if a negative context is present
                }
            }


            // Ensure confidence doesn't go below zero after penalties
            confidence = Math.max(0, confidence);


            if (confidence >= settings.minConfidenceThreshold) {
                updateStatus("Potential offer found! Waiting for your decision...", 'var(--highlight-color, #FFD700)');

                // Stop all bot activity (scrolling and processing)
                stopBot();

                post.dataset.sampleChecked = 'true';

                const postSnippet = postText.substring(0, Math.min(postText.length, 300));
                // targetLinkHref is already determined above

                // Pass the actual post element to the confirmation modal
                const userChoice = await showCustomConfirmation(post, postSnippet, isSponsored, targetLinkHref);

                if (userChoice === 'open_link') { // User chose to open the link
                    if (targetLinkHref) { // Double check link exists before opening
                        window.open(targetLinkHref, '_blank');
                        updateStatus("Opened link. Resuming bot...", 'var(--primary-color, #4CAF50)');
                    } else {
                        updateStatus("No link to open. Resuming bot...", 'var(--warning-color, #FFA500)');
                    }
                    // Automatically restart the bot after user interaction
                    setTimeout(() => {
                        startBot();
                    }, 0);
                } else if (userChoice === 'jump') { // User chose to jump to the post
                    updateStatus("Jumped to post. Bot stopped for inspection.", 'var(--warning-color, #FFA500)');
                    // The bot remains stopped here. No startBot() call.
                } else { // userChoice === 'dismiss' (User chose to dismiss and resume)
                    updateStatus("User chose to dismiss. Resuming bot...", 'var(--primary-color, #4CAF50)');
                    // Automatically restart the bot after user interaction
                    setTimeout(() => {
                        startBot();
                    }, 0);
                }
                return; // Stop processing other posts in this batch to avoid immediate re-trigger
            }
        }
    }

    // --- UI Creation ---
    function createUI() {
        const rootStyle = document.documentElement.style;
        // Define a cleaner, darker color palette
        rootStyle.setProperty('--primary-color', '#4CAF50'); // Green for success/start
        rootStyle.setProperty('--danger-color', '#E53935'); // Red for stop/danger
        rootStyle.setProperty('--text-color', '#E0E0E0'); // Light grey for general text
        rootStyle.setProperty('--bg-color', 'rgba(26, 26, 26, 0.9)'); // Dark, slightly transparent background
        rootStyle.setProperty('--border-color', 'rgba(60, 60, 60, 0.7)'); // Darker border
        rootStyle.setProperty('--shadow-color', 'rgba(0, 0, 0, 0.4)'); // Stronger shadow
        rootStyle.setProperty('--highlight-color', '#FFC107'); // Amber for highlights/warnings
        rootStyle.setProperty('--warning-color', '#FF9800'); // Orange for warnings
        rootStyle.setProperty('--settings-icon-color', '#64B5F6'); // Light blue for settings icon

        const uiContainer = document.createElement('div');
        Object.assign(uiContainer.style, {
            position: 'fixed', top: '20px', left: '20px',
            backgroundColor: 'var(--bg-color)',
            backdropFilter: 'blur(10px)', // Slightly more blur
            padding: '20px', // Increased padding
            border: '1px solid var(--border-color)',
            borderRadius: '16px', // More rounded
            boxShadow: '0 8px 20px var(--shadow-color)', // Stronger shadow
            zIndex: '99999',
            fontFamily: 'Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif', // Added Inter
            display: 'flex', flexDirection: 'column', gap: '20px', // Increased gap
            alignItems: 'center', justifyContent: 'center', minWidth: '260px', // Slightly wider
            // Initial state for fluid entry
            opacity: '0',
            transform: 'translateY(-30px)', // Larger initial translateY
            transition: 'opacity 0.6s ease-out, transform 0.6s ease-out', // Slower, smoother animation
            // Draggable styles
            cursor: 'grab',
            userSelect: 'none',
        });

        // --- Draggable UI Logic ---
        let isDragging = false;
        let initialX;
        let initialY;
        let xOffset = 0;
        let yOffset = 0;

        // Load saved position if available
        const savedPosition = JSON.parse(localStorage.getItem('instagramBotUIPosition'));
        if (savedPosition) {
            xOffset = savedPosition.x;
            yOffset = savedPosition.y;
            uiContainer.style.left = xOffset + 'px';
            uiContainer.style.top = yOffset + 'px';
        } else {
            // Set initial position if no saved position
            uiContainer.style.left = '20px';
            uiContainer.style.top = '20px';
        }


        uiContainer.addEventListener("mousedown", dragStart);
        document.addEventListener("mouseup", dragEnd);
        document.addEventListener("mousemove", drag);

        function dragStart(e) {
            const targetTagName = e.target.tagName;
            if (targetTagName === 'BUTTON' || targetTagName === 'INPUT' || targetTagName === 'LABEL' || e.target.closest('.tm-custom-modal-overlay')) {
                return;
            }

            initialX = e.clientX - uiContainer.getBoundingClientRect().left;
            initialY = e.clientY - uiContainer.getBoundingClientRect().top;

            isDragging = true;
            uiContainer.style.cursor = 'grabbing';
        }

        function dragEnd(e) {
            isDragging = false;
            uiContainer.style.cursor = 'grab';
            localStorage.setItem('instagramBotUIPosition', JSON.stringify({ x: xOffset, y: yOffset }));
        }

        function drag(e) {
            if (isDragging) {
                e.preventDefault();

                let newX = e.clientX - initialX;
                let newY = e.clientY - initialY;

                const viewportWidth = window.innerWidth;
                const viewportHeight = window.innerHeight;

                const uiWidth = uiContainer.offsetWidth;
                const uiHeight = uiContainer.offsetHeight;

                newX = Math.max(0, Math.min(newX, viewportWidth - uiWidth));
                newY = Math.max(0, Math.min(newY, viewportHeight - uiHeight));

                uiContainer.style.left = newX + "px";
                uiContainer.style.top = newY + "px";

                xOffset = newX;
                yOffset = newY;
            }
        }
        // --- End Draggable UI Logic ---


        const headerRow = document.createElement('div');
        Object.assign(headerRow.style, {
            display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%',
            marginBottom: '10px', // Added margin below header
        });
        uiContainer.appendChild(headerRow);

        const titleElement = document.createElement('h3');
        titleElement.innerText = `Instagram Bot v${SCRIPT_VERSION}`;
        Object.assign(titleElement.style, {
            margin: '0', color: 'var(--text-color)', fontSize: '24px', // Larger font
            fontWeight: '900', // Extra bold
            flexGrow: '1', textAlign: 'center',
            letterSpacing: '0.5px', // Added letter spacing
        });
        headerRow.appendChild(titleElement);

        // Settings Button
        const settingsButton = document.createElement('button');
        settingsButton.innerHTML = '⚙️';
        Object.assign(settingsButton.style, {
            background: 'none', border: 'none', fontSize: '32px', // Larger icon
            cursor: 'pointer', color: 'var(--settings-icon-color)',
            padding: '0',
            lineHeight: '1',
            transition: 'transform 0.2s ease, color 0.2s ease', // Smoother transition
        });
        settingsButton.onmouseover = () => settingsButton.style.transform = 'rotate(30deg) scale(1.1)'; // More pronounced hover
        settingsButton.onmouseout = () => settingsButton.style.transform = 'rotate(0deg) scale(1)';
        settingsButton.onclick = async () => {
            console.log("Settings button clicked.");
            try {
                await showSettingsModal();
            } catch (e) {
                console.error("Error executing showSettingsModal:", e);
                updateStatus("Error opening settings!", 'red');
            }
        };
        headerRow.appendChild(settingsButton);


        const buttonsContainer = document.createElement('div');
        Object.assign(buttonsContainer.style, {
            display: 'flex', gap: '15px', width: '100%', justifyContent: 'center', // Increased gap
        });
        uiContainer.appendChild(buttonsContainer);

        const buttonStyle = {
            padding: '12px 22px', // Increased padding
            border: 'none',
            borderRadius: '10px', // More rounded
            fontSize: '16px', // Larger font
            fontWeight: '700', // Bolder font
            cursor: 'pointer',
            transition: 'background-color 0.2s ease, transform 0.1s ease, box-shadow 0.2s ease', // Added box-shadow transition
            boxShadow: '0 4px 10px var(--shadow-color)', // Initial shadow
            flexGrow: '1',
            letterSpacing: '0.3px', // Added letter spacing
        };

        const startButton = document.createElement('button');
        startButton.innerText = 'Start Bot';
        Object.assign(startButton.style, buttonStyle, {
            backgroundColor: 'var(--primary-color)', color: 'white',
            backgroundImage: 'linear-gradient(145deg, #4CAF50, #388E3C)', // Subtle gradient
        });
        startButton.onmouseover = () => { startButton.style.backgroundColor = '#45a049'; startButton.style.boxShadow = '0 6px 15px rgba(76, 175, 80, 0.4)'; }; // Stronger hover shadow
        startButton.onmouseout = () => { startButton.style.backgroundColor = 'var(--primary-color)'; startButton.style.boxShadow = '0 4px 10px var(--shadow-color)'; };
        startButton.onmousedown = () => startButton.style.transform = 'translateY(2px)';
        startButton.onmouseup = () => startButton.style.transform = 'translateY(0)';
        startButton.onclick = () => {
            console.log("Start Bot button clicked.");
            try {
                startBot();
            } catch (e) {
                console.error("Error executing startBot:", e);
                updateStatus("Error starting bot!", 'red');
            }
        };
        buttonsContainer.appendChild(startButton);

        const stopButton = document.createElement('button');
        stopButton.innerText = 'Stop Bot';
        Object.assign(stopButton.style, buttonStyle, {
            backgroundColor: 'var(--danger-color)', color: 'white',
            backgroundImage: 'linear-gradient(145deg, #E53935, #C62828)', // Subtle gradient
        });
        stopButton.onmouseover = () => { stopButton.style.backgroundColor = '#da190b'; stopButton.style.boxShadow = '0 6px 15px rgba(229, 57, 53, 0.4)'; };
        stopButton.onmouseout = () => { stopButton.style.backgroundColor = 'var(--danger-color)'; stopButton.style.boxShadow = '0 4px 10px var(--shadow-color)'; };
        stopButton.onmousedown = () => stopButton.style.transform = 'translateY(2px)';
        stopButton.onmouseup = () => stopButton.style.transform = 'translateY(0)';
        stopButton.onclick = () => {
            console.log("Stop Bot button clicked.");
            try {
                stopBot();
            } catch (e) {
                console.error("Error executing stopBot:", e);
                updateStatus("Error stopping bot!", 'red');
            }
        };
        buttonsContainer.appendChild(stopButton);

        statusBarElement = document.createElement('div');
        Object.assign(statusBarElement.style, {
            marginTop: '10px', // Increased margin
            fontSize: '15px', // Slightly larger font
            fontWeight: 'bold', // Bolder text
            color: 'var(--text-color)',
            textAlign: 'center', width: '100%', paddingTop: '0px',
            textShadow: '0 1px 2px rgba(0,0,0,0.2)', // Subtle text shadow for pop
        });
        uiContainer.appendChild(statusBarElement);

        document.body.appendChild(uiContainer);

        // Animate the UI container into view
        requestAnimationFrame(() => {
            uiContainer.style.opacity = '1';
            uiContainer.style.transform = 'translateY(0)';
        });

        updateStatus("Ready for use. Click 'Start Bot'!", 'var(--text-color)');
    }

    // --- Initialization ---
    window.addEventListener('load', () => {
        loadSettings(); // Load settings first
        createUI();     // Then create the UI

        // Check if we should auto-start the bot after a previous forced reload (due to being stuck)
        if (localStorage.getItem(AUTO_START_FLAG) === 'true') {
            localStorage.removeItem(AUTO_START_FLAG); // Clear the flag immediately
            updateStatus("Restarting after recovery refresh...", 'green'); // Inform user
            // Give the page a moment to fully render before restarting the bot
            setTimeout(() => {
                startBot();
            }, 2000); // 2-second delay before auto-starting
        }
    });

})();