Nhentai Manga Loader

Loads nhentai manga chapters into one page in a long strip format with image scaling, click events, and a dark mode for reading.

// ==UserScript==
// @name         Nhentai Manga Loader
// @namespace    http://www.nhentai.net
// @version      6.0.11
// @author       longkidkoolstar
// @description  Loads nhentai manga chapters into one page in a long strip format with image scaling, click events, and a dark mode for reading.
// @match        https://nhentai.net/*
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// @icon         https://i.imgur.com/S0x03gs.png
// @grant        GM.getValue
// @grant        GM.setValue
// @grant        GM.deleteValue
// @grant        GM.listValues
// @license      MIT
// @noframes
// ==/UserScript==

(function() {
    'use strict';

    let loadedPages = 0; // Track loaded pages
    let totalPages = 0; // Track total pages
    let loadingImages = 0; // Track loading images
    let totalImages = 0; // Track total images
    let freshloadedcache = false;
    const mangaId = extractMangaId(window.location.href);

// Add this new function to handle jumping to pages
function handleJumpToPage(input) {
    const targetPage = parseInt(input.value);
    if (isNaN(targetPage) || targetPage < 1 || targetPage > totalPages) {
        alert(`Please enter a valid page number between 1 and ${totalPages}`);
        return;
    }

    const pageContainers = document.querySelectorAll('.manga-page-container');
    const targetContainer = Array.from(pageContainers).find(container => {
        const img = container.querySelector('img');
        return img && parseInt(img.alt.replace('Page ', '')) === targetPage;
    });

    if (targetContainer) {
        // Page is loaded, scroll to it
        if (/Mobi/i.test(navigator.userAgent)) {
            // Get the offset from the top of the document instead of viewport
            const offsetTop = targetContainer.offsetTop; // Add 5px to the top offset
            // Scroll to the absolute position
            window.scrollTo({
                top: offsetTop,
                left: 0,
                behavior: 'instant' // Use 'instant' for consistent behavior
            });
        } else {
            targetContainer.scrollIntoView({ behavior: 'smooth' });
        }
    } else {
        // Page not loaded, redirect to it
        const mangaId = extractMangaId(window.location.href);
        loadSpecificPage(targetPage, mangaId);
    }

    // Clear the input after jumping
    input.value = '';
}


    (async () => {
        const value = JSON.parse(localStorage.getItem('redirected'));
        if (value === null) {
            localStorage.setItem('redirected', JSON.stringify(false)); // Flag to track if the page has been redirected
        }
    })();

    // Helper to create custom style sheets for elements
    function addCustomStyles() {
        const style = document.createElement('style');
        style.innerHTML = `
            #manga-container {
                max-width: 100vw;
                margin: 0 auto;
                padding: 0;
            }
            .manga-page-container {
                position: relative;
                display: block;
                margin: 0;
            }
            .manga-page-container img {
                max-width: 100%;
                display: block;
                margin: 3px auto;
                border-radius: 0;
                transition: all 0.3s ease;
                box-shadow: none;
            }
            .ml-counter {
                background-color: #222;
                color: white;
                border-radius: 10px;
                width: 40px;
                margin-left: auto;
                margin-right: auto;
                margin-top: -8.8px;
                padding-left: 5px;
                padding-right: 5px;
                border: 1px solid white;
                z-index: 100;
                position: relative;
                font-size: 9px;
                font-family: 'Open Sans', sans-serif;
                top: 4px;
            }
            .exit-btn {
                background-color: #e74c3c;
                color: white;
                padding: 5px 10px;
                font-size: 14px;
                border: none;
                border-radius: 8px;
                cursor: pointer;
                box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
                margin: 10px auto;
                display: block;
                text-align: center;
            }
            .exit-btn:hover {
                background-color: #c0392b;
            }
            .exit-btn:active {
                background-color: #a93226;
            }
            .ml-stats {
                position: fixed;
                bottom: 10px;
                right: 10px;
                background-color: rgba(0, 0, 0, 0.8);
                color: white;
                border-radius: 8px;
                padding: 3px;
                z-index: 1000;
                font-family: 'Open Sans', sans-serif;
                display: flex;
                flex-direction: column;
                align-items: flex-start;
            }
            .ml-stats-content {
                display: flex;
                align-items: center;
                cursor: pointer;
            }
            .ml-button {
                cursor: pointer;
                margin-left: 5px;
            }
            .ml-box {
                display: none;
                background-color: #333;
                color: white;
                padding: 10px;
                border-radius: 5px;
                margin-top: 5px;
                width: 200px;
            }
        `;
        document.head.appendChild(style);
    }

//------------------------------------------------------------------------------**Remove this when transfer over to Nhentai+**------------------------------------------------------------------------------

let isPopupVisible = false; // Flag to track if the popup is visible

function showPopupForSavedPosition(message, onConfirm, options = {}) {
    const existingPopup = document.getElementById('popup');
    if (existingPopup) {
        document.body.removeChild(existingPopup);
    }

    const popup = document.createElement('div');
    popup.id = 'popup';
    popup.innerHTML = `
        <div class="popup-content" role="alert">
            <p>${message}</p>
            <button class="confirm-btn">${options.confirmText || 'Yes'}</button>
            <button class="cancel-btn">${options.cancelText || 'No'}</button>
        </div>
    `;
    document.body.appendChild(popup);

    // Set the popup visibility flag
    isPopupVisible = true; 

    // Add CSS styling for the popup
    const style = document.createElement('style');
    style.textContent = `
        #popup {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(0, 0, 0, 0.9);
            color: #fff;
            border-radius: 5px;
            z-index: 9999;
            padding: 15px;
            max-width: 300px;
            text-align: center;
        }
        .popup-content {
            position: relative;
            padding: 10px;
        }
        .confirm-btn,
        .cancel-btn {
            margin-top: 10px;
            background: none;
            border: none;
            color: #fff;
            font-size: 18px;
            cursor: pointer;
            transition: color 0.3s, transform 0.3s;
            margin: 0 5px; /* Space between buttons */
        }
        .confirm-btn:hover,
        .cancel-btn:hover {
            color: #ff0000; /* Change color on hover */
            transform: scale(1.1); /* Slightly enlarge on hover */
        }
    `;
    document.head.appendChild(style);

    // Handle confirmation button click
    document.querySelector('.confirm-btn').addEventListener('click', function() {
        document.body.removeChild(popup);
        document.head.removeChild(style);
        isPopupVisible = false; // Reset the flag when popup is closed
        if (onConfirm) onConfirm(); // Call the onConfirm callback
    });

    // Handle cancel button click
    document.querySelector('.cancel-btn').addEventListener('click', function() {
        document.body.removeChild(popup);
        document.head.removeChild(style);
        isPopupVisible = false; // Reset the flag when popup is closed
    });

    // Auto-close feature based on options
    const duration = options.duration || 10000; // Default to 10 seconds if not specified
    setTimeout(() => {
        if (document.body.contains(popup)) {
            document.body.removeChild(popup);
            document.head.removeChild(style);
            isPopupVisible = false; // Reset the flag when auto-closed
        }
    }, duration); // Use the specified duration
}



//------------------------------------------------------------------------------**Remove this when transfer over to Nhentai+**------------------------------------------------------------------------------

// Function to extract manga ID from URL
function extractMangaId(url) {
    const match = url.match(/\/g\/(\d+)/);
    return match ? match[1] : null;
}

function getCurrentPage(entry) {
  const pageElements = document.querySelectorAll('.manga-page-container');
  for (let i = 0; i < pageElements.length; i++) {
    if (entry.target === pageElements[i]) {
      const imgElement = pageElements[i].querySelector('img');
      const altText = imgElement.alt;
      const pageNumber = parseInt(altText.replace('Page ', ''));
      return pageNumber;
    }
  }
  return 1; // Default to page 1 if no current page is found
}


    // Create the "Exit" button
    function createExitButton() {
        const button = document.createElement('button');
        button.textContent = 'Exit';
        button.className = 'exit-btn';
        return button;
    }

    // Add page counter below the image
    function addPageCounter(pageNumber) {
        const counter = document.createElement('div');
        counter.className = 'ml-counter';
        counter.textContent = `${pageNumber}`;
        return counter;
    }

    // Update stats display
    function updateStats() {
        const statsContainer = document.querySelector('.ml-stats-pages');
        const statsBox = document.querySelector('.ml-floating-msg');
        if (statsBox && !statsBox.querySelector('.jump-controls')) {
            statsBox.innerHTML = `<strong>Stats:</strong>
<span class="ml-loading-images">${loadingImages} images loading</span>
<span class="ml-total-images">${totalImages} images in chapter</span>
<span class="ml-loaded-pages">${loadedPages} pages parsed</span>`;

if (statsContainer) {
    statsContainer.textContent = `${loadedPages}/${totalPages} loaded`;
}
        }
    }

// Declare reloadMode at the top level
let reloadMode = false; // Flag to track reload mode

async function createStatsWindow() {
    const statsWindow = document.createElement('div');
    statsWindow.className = 'ml-stats';

    // Use a wrapper to keep the button and content aligned
    const statsWrapper = document.createElement('div');
    statsWrapper.style.display = 'flex';
    statsWrapper.style.alignItems = 'center'; // Center vertically

    const collapseButton = document.createElement('span');
    collapseButton.className = 'ml-stats-collapse';
    collapseButton.title = 'Hide stats';
    collapseButton.textContent = '>>';
    collapseButton.style.cursor = 'pointer';
    collapseButton.style.marginRight = '10px'; // Space between button and content
    collapseButton.addEventListener('click', async function() {
        contentContainer.style.display = contentContainer.style.display === 'none' ? 'block' : 'none';
        collapseButton.textContent = contentContainer.style.display === 'none' ? '<<' : '>>';

        // Save the collapse state
        await GM.setValue('statsCollapsed', contentContainer.style.display === 'none');
    });

    const contentContainer = document.createElement('div');
    contentContainer.className = 'ml-stats-content';

    const statsText = document.createElement('span');
    statsText.className = 'ml-stats-pages';
    statsText.textContent = `0/0 loaded`; // Initial stats

    const infoButton = document.createElement('i');
    infoButton.innerHTML = '<i class="fas fa-question-circle"></i>';
    infoButton.title = 'See userscript information and help';
    infoButton.style.marginLeft = '5px';
    infoButton.style.marginRight = '5px'; // Add space to the right
    infoButton.addEventListener('click', function() {
        alert('This userscript loads manga pages in a single view. It is intended to be used for manga reading and saves your previous scroll position amongst other features.');
    });
    
    const moreStatsButton = document.createElement('i');
    moreStatsButton.innerHTML = '<i class="fas fa-chart-pie"></i>';
    moreStatsButton.title = 'See detailed page stats';
    moreStatsButton.style.marginRight = '5px';
    moreStatsButton.addEventListener('click', function() {
        const statsBox = document.querySelector('.ml-floating-msg');
        
        // If stats box is showing stats content, close it
        if (statsBox.style.display === 'block' && statsBox.querySelector('strong').textContent === 'Stats:') {
            statsBox.style.display = 'none';
            return;
        }
        
        // Show stats content
        statsBox.style.display = 'block';
        statsBox.innerHTML = `<strong>Stats:</strong>
<span class="ml-loading-images">${loadingImages} images loading</span>
<span class="ml-total-images">${totalImages} images in chapter</span>
<span class="ml-loaded-pages">${loadedPages} pages parsed</span>`;
    });
    
    // Add new jump page button
    const jumpPageButton = document.createElement('i');
    jumpPageButton.innerHTML = '<i class="fas fa-search"></i>';
    jumpPageButton.title = 'Toggle jump to page';
    jumpPageButton.style.marginRight = '5px';
    jumpPageButton.addEventListener('click', function() {
        const statsBox = document.querySelector('.ml-floating-msg');
        
        // If stats box is showing jump page content, close it
        if (statsBox.style.display === 'block' && statsBox.querySelector('strong').textContent === 'Jump to Page') {
            statsBox.style.display = 'none';
            return;
        }
        
        // Show jump page content
        statsBox.style.display = 'block';
        statsBox.innerHTML = `<strong>Jump to Page</strong>
<div class="jump-controls" style="display: flex; gap: 5px; margin: 5px 0;">
    <button class="jump-first">First</button>
    <input type="number" class="jump-input" min="1" max="${totalPages}" placeholder="1-${totalPages}">
    <button class="jump-last">Last</button>
</div>
<button class="load-saved-position">Load Saved Position</button> 
<button class="jump-go">Go</button>`;

        // Style the input and buttons
        const jumpInput = statsBox.querySelector('.jump-input');
        jumpInput.style.cssText = `
            flex: 2;
            width: 50px;
            background: #444;
            color: #fff;
            border: 1px solid #555;
            border-radius: 4px;
            padding: 2px 4px;
        `;

        // Style all buttons consistently
        const buttons = statsBox.querySelectorAll('button');
        buttons.forEach(button => {
            button.style.cssText = `
                background-color: #444;
                color: #fff;
                border: 1px solid #555;
                border-radius: 4px;
                padding: 2px 6px;
                cursor: pointer;
                transition: background-color 0.2s;
                width: 100%;
                margin-top: 5px;
                text-align: left;
            `;
        });

        // Special styling for First/Last buttons
        const firstLastButtons = statsBox.querySelectorAll('.jump-first, .jump-last');
        firstLastButtons.forEach(button => {
            button.style.cssText += `
                flex: 1;
                margin-top: 0;
                width: auto;
            `;
        });

        // Add event listeners
        const loadSavedPositionbtn =  statsBox.querySelector('.load-saved-position')
        const jumpGo = statsBox.querySelector('.jump-go');
        const jumpFirst = statsBox.querySelector('.jump-first');
        const jumpLast = statsBox.querySelector('.jump-last');

        loadSavedPositionbtn.addEventListener('click', () => loadSavedPosition(mangaId));
        jumpGo.addEventListener('click', () => handleJumpToPage(jumpInput));
        jumpFirst.addEventListener('click', () => handleJumpToPage({ value: '1' }));
        jumpLast.addEventListener('click', () => handleJumpToPage({ value: totalPages.toString() }));


    });

    const refreshButton = document.createElement('i');
    refreshButton.innerHTML = '<i class="fas fa-sync-alt"></i>';
    refreshButton.title = 'Click an image to reload it.';
    refreshButton.addEventListener('click', function() {
        reloadMode = !reloadMode;
        refreshButton.style.color = reloadMode ? 'orange' : '';
        console.log(`Reload mode is now ${reloadMode ? 'enabled' : 'disabled'}.`);
    });

// Add the mini exit button for refreshing the page
const miniExitButton = document.createElement('button');
miniExitButton.innerHTML = '<i class="fas fa-sign-out-alt"></i>';  // Font Awesome icon for sign out
miniExitButton.title = 'Exit the Manga Loader';
miniExitButton.style.marginLeft = '10px'; // Space between other buttons
miniExitButton.style.backgroundColor = '#e74c3c';  // Red color for the button
miniExitButton.style.color = '#fff';
miniExitButton.style.border = 'none';
miniExitButton.style.padding = '2px 5px';
miniExitButton.style.borderRadius = '5px';
miniExitButton.style.cursor = 'pointer';

// Refresh the page when the button is clicked
miniExitButton.addEventListener('click', function() {
    window.location.reload();  // Refresh the page
});

    // Append all elements to the stats content container
    contentContainer.appendChild(statsText);
    contentContainer.appendChild(infoButton);
    contentContainer.appendChild(moreStatsButton);
    contentContainer.appendChild(jumpPageButton); // Add the new button
    contentContainer.appendChild(refreshButton);
    contentContainer.appendChild(miniExitButton);

    statsWrapper.appendChild(collapseButton);
    statsWrapper.appendChild(contentContainer);
    statsWindow.appendChild(statsWrapper);

    const statsBox = document.createElement('pre');
    statsBox.className = 'ml-box ml-floating-msg';
    statsBox.style.display = 'none'; // Initially hidden

    // Create the stats content
    const statsContent = `<strong>Stats:</strong>
<span class="ml-loading-images">0 images loading</span>
<span class="ml-total-images">0 images in chapter</span>
<span class="ml-loaded-pages">0 pages parsed</span>`;

    statsBox.innerHTML = statsContent;
    statsWindow.appendChild(statsBox);

    // Check and set initial collapse state
    const collapsed = await GM.getValue('statsCollapsed', false);
    if (collapsed) {
        contentContainer.style.display = 'none';
        collapseButton.textContent = '<<'; // Change to indicate expanded state
    }

    // Add hover effect
    statsWindow.style.transition = 'opacity 0.3s';
    statsWindow.style.opacity = '0.6'; // Dimmed by default

    statsWindow.addEventListener('mouseenter', function() {
        statsWindow.style.opacity = '1'; // Fully visible on hover
    });

    statsWindow.addEventListener('mouseleave', function() {
        statsWindow.style.opacity = '0.6'; // Dim again on mouse leave
    });

    document.body.appendChild(statsWindow);
}
 



// Add the click event to images
function addClickEventToImage(image) {
    image.addEventListener('click', function() {
        if (reloadMode) {
            const imgSrc = image.dataset.src || image.src;
            image.src = ''; // Clear the src to trigger reload
            setTimeout(() => {
                image.src = imgSrc; // Retry loading after clearing
            }, 100); // Short delay to ensure proper reload
        }
    });
}



    // Function to hide specified elements
    function hideElements() {
        const elementsToHide = ['#image-container', '#content', 'nav'];
        elementsToHide.forEach(selector => {
            const element = document.querySelector(selector);
            if (element) {
                element.style.display = 'none';
            }
        });
    }

// Add this at the top level to track image loading status
const imageStatus = []; // Array to track the status of each image

// Add an event listener to detect when the user scrolls
window.addEventListener('scroll', logCurrentPage);

// Variable to store the previous page
let previousPage = 0;

// Function to log the current page
function logCurrentPage() {
    // Check if the URL matches the desired pattern
    if (!window.location.href.match(/^https:\/\/nhentai\.net\/g\//)) {
        return; // Exit if the URL is not correct
    }

    // Check if the download button exists
    if (document.querySelector("#download")) {
        return; // Exit if the download button exists
    }

    const currentPage = getCurrentVisiblePage();
    const totalPages = document.querySelectorAll('.manga-page-container').length;

    // Check if the Load Manga button exists
    const loadMangaButton = document.querySelector('.load-manga-btn');
    if (loadMangaButton) {
        return; // Exit if the Load Manga button exists
    }

    if ((currentPage === totalPages - 1 || currentPage === totalPages) && (!isPopupVisible || freshloadedcache)) {
        //console.log(`Current page: ${currentPage}`);
        previousPage = currentPage;
        if (currentPage >= totalPages - 1) deleteMangaFromStorage();
    }
}

function getCurrentVisiblePage() {
    const pageContainers = document.querySelectorAll('.manga-page-container');
    let visiblePage = 0;
    const totalPages = pageContainers.length;
    
    // No pages found
    if (totalPages === 0) {
       // console.warn('No page containers found.');
        return visiblePage;
    }
    
    // Determine if device is mobile or desktop based on screen width
    const isMobile = window.innerWidth <= 768; // Common breakpoint for mobile
    
    // Use different thresholds based on device type
    const visibilityThreshold = isMobile ? 70 : 25; // Lower threshold for desktop
    
    pageContainers.forEach((container, index) => {
        const img = container.querySelector('img');
        if (img && img.alt) {
            const pageNumber = parseInt(img.alt.replace('Page ', ''), 10);
            const rect = img.getBoundingClientRect();
            const pageHeight = rect.bottom - rect.top;
            const visibleHeight = Math.min(window.innerHeight, rect.bottom) - Math.max(0, rect.top);
            const visiblePercentage = (visibleHeight / pageHeight) * 100;
            
            if (visiblePercentage >= visibilityThreshold) {
                visiblePage = pageNumber;
            }
            
            // Keep the last page logic
            if (index + 1 === totalPages && visiblePercentage >= 10) {
                visiblePage = totalPages;
            }
        }
    });
    
    // Fallback logic remains the same
    if (visiblePage === 0) {
        const currentPageMatch = window.location.pathname.match(/\/g\/\d+\/(\d+)/);
        if (currentPageMatch) {
            visiblePage = parseInt(currentPageMatch[1], 10);
        }
    }
    
    //console.log("Current visible page determined:", visiblePage);
    return visiblePage;
}


// Function to delete manga from storage
function deleteMangaFromStorage() {
    const mangaId = window.location.pathname.match(/\/g\/(\d+)/)[1];
    GM.deleteValue(mangaId); // Delete the manga entry

    // Check if metadata exists before attempting to delete it
    GM.getValue(`metadata_${mangaId}`).then(metadata => {
        if (metadata) {
            GM.deleteValue(`metadata_${mangaId}`); // Delete the associated metadata
            console.log(`Metadata for manga ${mangaId} deleted from storage`);
        } else {
            console.log(`No metadata found for manga ${mangaId}, skipping deletion`);
        }
    });

    console.log(`Manga ${mangaId} deleted from storage`);
}

// Replace the addScrollListener function with the following code
let previousPagex = 0; // Initialize previousPage at the top level

window.addEventListener('scroll', async () => {
    const currentPage = getCurrentVisiblePage();
    //console.log("current page:", currentPage, "last page", previousPagex);
    // Only save the current page if the popup is not visible and the current page is greater than the previous page
    if (!isPopupVisible || freshloadedcache) {
        if (currentPage > previousPagex) {
            console.log(`Current page: ${currentPage}, Previous page: ${previousPagex}`);
            await saveCurrentPosition(mangaId, currentPage);
            previousPagex = currentPage; // Update previousPage to the current page
        }
    }
});
// Log the state of freshloadedcache every second
setInterval(() => {
   // console.log(`Fresh loaded cache state: ${freshloadedcache}`);
}, 1000);


// Load all manga images with page separators and scaling
function loadMangaImages(mangaId) {
    hideElements();
    createStatsWindow(); // Create the stats window

    const mangaContainer = document.createElement('div');
    mangaContainer.id = 'manga-container';
    document.body.appendChild(mangaContainer);
    

    

    const exitButtonTop = createExitButton();
    mangaContainer.appendChild(exitButtonTop);

    totalPages = parseInt(document.querySelector('.num-pages').textContent.trim());
    totalImages = totalPages; // Update total images for stats
    const initialPage = parseInt(window.location.href.match(/\/g\/\d+\/(\d+)/)[1]);
    let currentPage = initialPage;

    // Queue for tracking loading images
    const loadingQueue = [];
    const maxConcurrentLoads = /Mobi/.test(navigator.userAgent) ? 10 : 40; // Maximum number of concurrent image loads

    // Helper to create the page container with images
    function createPageContainer(pageNumber, imgSrc) {
        const container = document.createElement('div');
        container.className = 'manga-page-container';

        // Create the actual image element
        const img = document.createElement('img');
        img.src = ''; // Start with empty src to avoid loading it immediately
        img.dataset.src = imgSrc; // Store the actual src in data attribute
        img.alt = `Page ${pageNumber}`;

        // Add page counter below the image
        const pageCounter = addPageCounter(pageNumber);

        // Append the image and page counter
        container.appendChild(img);
        container.appendChild(pageCounter); // <-- Page number is shown here

          // Add exit button to the bottom of the last loaded page
    if (pageNumber === totalPages) {
        const exitButton = createExitButton();
        container.appendChild(exitButton);
        exitButton.addEventListener('click', () => {
            window.location.reload();
        })
    }

        // Track the image status
        imageStatus[pageNumber] = { src: imgSrc, loaded: false, attempts: 0 };

        // Error handling and event listeners
        addErrorHandlingToImage(img, imgSrc, pageNumber);
        addClickEventToImage(img);
        mangaContainer.appendChild(container);

        loadedPages++; // Increment loaded pages count
        updateStats(); // Update stats display

        observePageContainer(container); // Observe for lazy loading

        // Save scroll position as soon as page container is created
        const mangaId = extractMangaId(window.location.href);
        const currentPage = getCurrentVisiblePage(); // Get the current visible page number
        if (!isPopupVisible || freshloadedcache) {
           // console.log("load again");
           // saveCurrentPosition(mangaId, currentPage);
        }
        

        // Start loading the actual image
        img.src = imgSrc; // Set the src to load the image

        // Mark as loaded on load
        img.onload = () => {
            imageStatus[pageNumber].loaded = true; // Mark as loaded
            loadingImages--; // Decrement loading images count
            updateStats(); // Update loading images count
        };

        return container;
    }







// Add a delay function
function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

// Track if the app is online or offline
let isOnline = navigator.onLine;

// Add event listeners to detect connection state changes
window.addEventListener('offline', () => {
    console.warn('You are offline. Pausing image loading.');
    isOnline = false;
});

window.addEventListener('online', () => {
    console.log('Back online. Resuming image loading.');
    isOnline = true;
    if (loadingQueue.length > 0) {
        processQueue(); // Resume processing the queue
    } else {
        // If queue is empty, manually trigger the next page load
        loadNextBatchOfImages(); // Load the next set of images if queue is empty
    }
});

// Load a single page with error handling, retry logic, and caching
async function loadPage(pageNumber, pageUrl, retryCount = 0) {
    if (loadingImages >= maxConcurrentLoads || !isOnline) {
        return; // Exit if we're at max concurrent loads or offline
    }

    loadingImages++;
    updateStats(); // Update loading images count

    const mangaId = extractMangaId(pageUrl);
    if (!mangaId) {
        console.error(`Could not extract manga ID from URL: ${pageUrl}`);
        loadingImages--;
        updateStats();
        handleFailedImage(pageNumber);
        return;
    }

    // Check cache first
    const cachedImage = getImageFromCache(pageNumber, mangaId);
    if (cachedImage && cachedImage.mangaId === mangaId) {
        console.log(`Loading page ${pageNumber} from cache for manga ${mangaId}`);
        const pageContainer = createPageContainer(pageNumber, cachedImage.imgSrc);
        imageStatus[pageNumber].loaded = true; // Mark as loaded
        
        // Ensure position is saved for cached pages
        const currentPage = pageNumber;
       // console.log("load");
       // saveCurrentPosition(mangaId, currentPage); // Save the position for cached pages
        
        

        loadingImages--;
        updateStats(); // Update loading images count

        // Pre-fetch the next page if it's not the last page
        if (pageNumber < totalPages && cachedImage.nextLink) {
            loadingQueue.push({ pageNumber: pageNumber + 1, pageUrl: cachedImage.nextLink });
            processQueue(); // Check the queue
        }
        return;
    }

    try {
        const response = await fetch(pageUrl);

        if (response.status === 429) {
            if (retryCount < maxRetries) {
                console.warn(`Rate limit exceeded for page ${pageNumber}. Retrying in ${retryDelay} ms...`);
                await delay(retryDelay); // Wait before retrying
                loadPage(pageNumber, pageUrl, retryCount + 1); // Retry loading the same page
                return;
            } else {
                console.error(`Failed to load page ${pageNumber} after ${maxRetries} attempts.`);
                loadingImages--;
                updateStats(); // Update loading images count
                handleFailedImage(pageNumber); // Handle failed image loading
                return;
            }
        }

        const html = await response.text();
        const parser = new DOMParser();
        const doc = parser.parseFromString(html, 'text/html');
        const imgElement = doc.querySelector('#image-container > a > img');
        const nextLink = doc.querySelector('#image-container > a').href;
        const imgSrc = imgElement.getAttribute('data-src') || imgElement.src;

        // Save to cache
        saveImageToCache(pageNumber, imgSrc, nextLink, mangaId);

        const pageContainer = createPageContainer(pageNumber, imgSrc);
        imageStatus[pageNumber].loaded = true; // Mark as loaded

        loadingImages--;
        updateStats(); // Update loading images count

        // Pre-fetch the next page once the current one loads
        if (pageNumber < totalPages && nextLink) {
            loadingQueue.push({ pageNumber: pageNumber + 1, pageUrl: nextLink });
            processQueue(); // Check the queue
        }
    } catch (err) {
        loadingImages--;
        console.error(err);
        updateStats(); // Update loading images count
        handleFailedImage(pageNumber); // Handle failed image loading
    }
}

// In your processing queue, ensure a delay ONLY after 429 status
async function processQueue() {
    while (loadingQueue.length > 0 && loadingImages < maxConcurrentLoads && isOnline) {
        const { pageNumber, pageUrl } = loadingQueue.shift(); // Get the next page to load
        loadPage(pageNumber, pageUrl); // Load it
    }
}

// Manually trigger the next batch of images if needed
function loadNextBatchOfImages() {
    if (loadingQueue.length === 0 && isOnline) {
        const nextPageNumber = getNextPageNumber(); // Logic to get the next page number
        const nextPageUrl = getNextPageUrl(nextPageNumber); // Logic to get the next page URL

        if (nextPageUrl) {
            loadingQueue.push({ pageNumber: nextPageNumber, pageUrl: nextPageUrl });
            processQueue(); // Resume loading
        }
    }
}

// Configuration for retry logic
const maxRetries = 5; // Maximum number of retries for rate limit
const retryDelay = 5000; // Delay in milliseconds before retrying only on 429 status





    // Handle failed image loading attempts
    function handleFailedImage(pageNumber) {
        if (imageStatus[pageNumber]) {
            imageStatus[pageNumber].attempts++;
            if (imageStatus[pageNumber].attempts <= 3) { // Retry up to 3 times
                console.warn(`Retrying to load image for page ${pageNumber}...`);
                loadPage(pageNumber, document.querySelector(`#image-container > a`).href); // Reattempt loading the same page
            } else {
                console.error(`Failed to load image for page ${pageNumber} after 3 attempts.`);
            }
        }
    }



const firstImageElement = document.querySelector('#image-container > a > img');
const firstImgSrc = firstImageElement.getAttribute('data-src') || firstImageElement.src;
createPageContainer(currentPage, firstImgSrc);

const firstImageLink = document.querySelector('#image-container > a').href;
loadingQueue.push({ pageNumber: currentPage + 1, pageUrl: firstImageLink }); // Add to queue
processQueue(); // Start processing the queue

// Observe all image containers for lazy loading
observeAndPreloadImages(); // <-- Add this here to track and lazy-load images

exitButtonTop.addEventListener('click', function() {
    window.location.reload();
});
}

// Pre-load next few images while user scrolls
function observeAndPreloadImages() {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          const imgElement = entry.target.querySelector('img');
          if (imgElement && imgElement.dataset.src) {
            imgElement.src = imgElement.dataset.src; // Load the image
            observer.unobserve(entry.target); // Stop observing after loading
  
            // Save the current position
            const mangaId = extractMangaId(window.location.href);
            const currentPage = getCurrentVisiblePage(entry); // Get the current page number
            if (!isPopupVisible || freshloadedcache) {
                //console.log("preload");
               // saveCurrentPosition(mangaId, currentPage);
            }
          }
        }
      });
    }, {
      rootMargin: '300px 0px', // Load images 300px before they appear
      threshold: 0.1
    });
  
    // Observe each image container
    const imageContainers = document.querySelectorAll('.manga-page-container');
    imageContainers.forEach((container) => observer.observe(container));
  }

// Function to get image data from local storage
function getImageFromCache(pageNumber, mangaId) {
    // console.log("freshloadedcache", freshloadedcache);
     freshloadedcache = true;
     setInterval(() => {
         //console.log("freshloadedcache", freshloadedcache);
         freshloadedcache = false;
     }, 3000)
     const cacheKey = `imagePage_${mangaId}_${pageNumber}`;
     const cachedData = localStorage.getItem(cacheKey);
     if (cachedData) {
         return JSON.parse(cachedData);
     }
     return null;
 }

// Function to save image data to local storage
function saveImageToCache(pageNumber, imgSrc, nextLink, mangaId) {
    const cacheKey = `imagePage_${mangaId}_${pageNumber}`;
    const cacheData = { imgSrc, nextLink, timestamp: Date.now(), mangaId };
    localStorage.setItem(cacheKey, JSON.stringify(cacheData));
}


  function addErrorHandlingToImage(image, imgSrc, pageNumber) {
    const subdomains = ['i1', 'i2', 'i3', 'i4', 'i5', 'i7']; // Add the alternative subdomains here
    let currentSubdomainIndex = 0;

    function updateImageSource(newSrc) {
        image.src = newSrc;
        image.dataset.src = newSrc; // Update data-src attribute
        updateImageCache(newSrc);
    }

    function updateImageCache(newSrc) {
        const mangaId = extractMangaId(window.location.href);
        const cachedData = getImageFromCache(pageNumber, mangaId);
        if (cachedData) {
            cachedData.imgSrc = newSrc;
            saveImageToCache(pageNumber, newSrc, cachedData.nextLink, mangaId);
            console.log(`Updated cache for page ${pageNumber} with new URL: ${newSrc}`);
        }
    }

    image.onerror = function() {
        console.warn(`Failed to load image: ${imgSrc} on page ${pageNumber}. Retrying...`);
        
        if (!imageStatus[pageNumber].retryCount) {
            imageStatus[pageNumber].retryCount = 0;
        }

        if (imageStatus[pageNumber].retryCount < subdomains.length) {
            imageStatus[pageNumber].retryCount++;
            
            const newSubdomain = subdomains[currentSubdomainIndex];
            const newImgSrc = imgSrc.replace(/i\d/, newSubdomain);

            currentSubdomainIndex = (currentSubdomainIndex + 1) % subdomains.length;
            console.log(`Retrying with new subdomain: ${newSubdomain} for page ${pageNumber}`);
            
            setTimeout(() => {
                updateImageSource(newImgSrc);
                // Update the local storage cache for this page
                const mangaId = extractMangaId(window.location.href);
                const cachedData = getImageFromCache(pageNumber, mangaId);
                if (cachedData) {
                    saveImageToCache(pageNumber, newImgSrc, cachedData.nextLink, mangaId);
                    console.log(`Updated local storage cache for page ${pageNumber} with new URL: ${newImgSrc}`);
                }
            }, 1000);
        } else {
            console.error(`Failed to load image on page ${pageNumber} after multiple attempts.`);
            image.alt = `Failed to load page ${pageNumber}`;
        }
    };

    // Update cache even if image loads successfully from cache
    image.onload = function() {
        updateImageCache(image.src);
    };
}


    // Create an IntersectionObserver to prioritize loading images that are in or near the viewport
// Create an IntersectionObserver to prioritize loading images that are in or near the viewport
const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            const imgElement = entry.target.querySelector('img');
            if (imgElement && imgElement.dataset.src) {
                imgElement.src = imgElement.dataset.src; // Load the image
                observer.unobserve(entry.target); // Stop observing after loading
                
                // Save the current scroll position as soon as the image starts loading
                const mangaId = extractMangaId(window.location.href);
                const currentPage = getCurrentVisiblePage(); // Get the current visible page number
                if (!isPopupVisible || freshloadedcache) {
                    //console.log("intesect");
                   // saveCurrentPosition(mangaId, currentPage);
                }
            }
            
        }
    });
}, {
    rootMargin: '200px 0px', // Adjust for preloading images slightly outside the viewport
    threshold: 0.1 // Trigger loading when 10% of the image is in view
});

function observePageContainer(container) {
    observer.observe(container); // Observe each page container for lazy loading
}

    
    addCustomStyles();


// Compress data into a string format
function compressData(data) {
    return JSON.stringify(data);
}

// Decompress data from string format
function decompressData(data) {
    return JSON.parse(data);
}

async function storeData(mangaId, pageNum) {
  const existingData = await retrieveData(mangaId);
  const existingPageNum = existingData ? existingData.pageNum : 0;

  if (pageNum > existingPageNum) {
    const currentTime = Date.now();
    const data = { pageNum, lastAccessed: currentTime };
    const compressedData = compressData(data);
    await GM.setValue(mangaId, compressedData);

    // Manage storage size if it exceeds the limit
    await manageStorage();
  }
}

// Retrieve data from Tampermonkey storage
async function retrieveData(mangaId) {
    const compressedData = await GM.getValue(mangaId, null);
    if (compressedData) {
        return decompressData(compressedData);
    }
    return null;
}

// Delete the least recently accessed data if the limit is reached
async function manageStorage() {
    const MAX_ENTRIES = 52;  // Limit to store 50 recent hentai
    const keys = await GM.listValues();
    if (keys.length > MAX_ENTRIES) {
        const entries = [];
        for (let key of keys) {
            const value = await GM.getValue(key);
            const data = decompressData(value);
            entries.push({ key, lastAccessed: data.lastAccessed });
        }

        // Sort by last accessed time, oldest first
        entries.sort((a, b) => a.lastAccessed - b.lastAccessed);

        // Remove the oldest entries until we're under the limit
        const excess = entries.length - MAX_ENTRIES;
        for (let i = 0; i < excess; i++) {
            await GM.deleteValue(entries[i].key);
        }
    }
}

let isRestoringPosition = false; // Flag to prevent overwriting saved position



function getCurrentPageFromURL() {
    const match = window.location.pathname.match(/\/g\/\d+\/(\d+)\//);
    return match ? parseInt(match[1], 10) : 1; // Default to 1 if not found
}


async function loadSavedPosition(mangaId) {
    console.log(`Trying to load saved position for: ${mangaId}`);

    const savedData = await retrieveData(mangaId);
    console.log(`Saved data retrieved:`, savedData); // Log the retrieved data

    const savedPage = savedData.pageNum;
    if (savedPage && savedPage === totalPages) {
        await GM.deleteValue(mangaId);
        console.log(`Saved position deleted for ${mangaId} since it's equal to total pages.`);
        return;
    }

    if (savedData) {
        const savedPage = savedData.pageNum;
        console.log(`Saved page is: ${savedPage}`); // Log the saved page number

        const currentPage = getCurrentPageFromURL(); // Get current page from URL
        console.log(`Current page is: ${currentPage}`); // Log the current page number

        // Only proceed if the saved page is different from the current one
        if (savedPage && savedPage !== currentPage) {
            console.log(`Restoring to saved page: ${savedPage}`);
            isRestoringPosition = true; // Set the flag before restoring the position

            const pageContainers = document.querySelectorAll('.manga-page-container');
            console.log(`Total page containers loaded: ${pageContainers.length}`); // Log how many pages are loaded

            if (pageContainers.length > 0) {
                // Directly scroll to the saved page
                scrollToSavedPage(pageContainers, savedPage);
            } else {
                console.log(`Waiting for pages to load...`);
                waitForPageContainers(savedPage); // Use a MutationObserver to wait for containers
            }
        } else {
            console.log(`Not restoring saved position for ${mangaId}. Current page is the same as saved page.`);
        }
    } else {
        console.log(`No saved position found for ${mangaId}.`);
    }

    isRestoringPosition = false; // Reset the flag after restoring the position
}

function waitForPageContainers(savedPageWithOffset) {
    const observer = new MutationObserver((mutations, obs) => {
        const pageContainers = document.querySelectorAll('.manga-page-container');
        if (pageContainers.length >= savedPageWithOffset) {
            console.log(`Page containers are now loaded: ${pageContainers.length}`);
            obs.disconnect(); // Stop observing once the pages are loaded
            scrollToSavedPage(pageContainers, savedPageWithOffset); // Scroll to the saved page
        }
    });

    // Observe changes in the DOM (specifically looking for added nodes)
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
}

// Queue for specific page loading
const specificPageQueue = [];



// Function to load a specific page by redirecting to its URL
async function loadSpecificPage(pageNumber) {
    const mangaId = extractMangaId(window.location.href); // Extract manga ID from current URL
    const pageUrl = `https://nhentai.net/g/${mangaId}/${pageNumber}/`; // Construct the URL for the specific page

    console.log(`Redirecting to page ${pageNumber} at URL: ${pageUrl}`);
    
    localStorage.setItem('redirected', 'true'); // Save the redirected state in local storage
    console.log(`Set redirected flag to true in storage.`); // Log confirmation of setting the flag

    window.location.href = pageUrl; // Redirect to the specific page URL
}

// Function to check if the page is redirected and load manga images
async function checkRedirected() {
    const wasRedirected = JSON.parse(localStorage.getItem('redirected') || 'false'); // Retrieve the redirected state

    if (wasRedirected) {
        const mangaId = extractMangaId(window.location.href);
        console.log(`Loading manga images for manga ID: ${mangaId}`); // Log the manga ID
        loadMangaButton.remove(); // Remove the load manga button since we already did it
        loadMangaImages(mangaId); // Call loadMangaImages after redirection
        console.log(`Reset redirected flag to false in storage.`); // Log confirmation of resetting the flag
        localStorage.setItem('redirected', JSON.stringify(false)); // Reset the flag in storage
    }
}
// Call the function every second
setInterval(checkRedirected, 1000);





async function scrollToSavedPage(pageContainers, savedPage, savedImgSrc) {
    const currentPage = getCurrentPageFromURL(); // Get current page number from URL
    const savedPageIndex = savedPage - currentPage; // Calculate the effective saved page index

    console.log(`Current page: ${currentPage}, Adjusted index for saved page: ${savedPageIndex}`);

    // Check if the adjusted index is out of bounds
    if (savedPageIndex < 0 || savedPageIndex >= pageContainers.length) {
        console.warn(`Adjusted saved page index ${savedPageIndex} is out of bounds.`);
        console.log(`Page ${savedPage} is not loaded yet. Redirecting to its URL.`);
        loadSpecificPage(savedPage); // Redirect to the specific page
        return; // Exit early
    }

    const savedPageElement = pageContainers[savedPageIndex]; // Get the container for the saved page
    const img = savedPageElement.querySelector('img');


        // If the image is loaded
        if (img && img.complete) {
            console.log(`Image for page ${savedPage} loaded. Moving to it.`);
            if (/Mobi/i.test(navigator.userAgent)) {
            const rect = savedPageElement.getBoundingClientRect();
            window.scrollTo(rect.left, rect.top); // teleport on mobile
            } else {
            savedPageElement.scrollIntoView({ behavior: 'smooth' }); // scroll on desktop
            }
        } else {
        console.log(`Image for page ${savedPage} not loaded yet. Redirecting to its URL.`);
        loadSpecificPage(savedPage); // Redirect to the specific page
    }
}

//----------------------------------------------Make the second option later When in Main Script----------------------------------------------

        // If the image is loaded
        // if (img && img.complete) {
        //     console.log(`Image for page ${savedPage} loaded. Scrolling to it.`);
        //     savedPageElement.scrollIntoView({ behavior: 'smooth' });
        // }


        // If the image is loaded
        // if (img && img.complete) {
        //     console.log(`Image for page ${savedPage} loaded. Scrolling to it.`);
        //     savedPageElement.scrollIntoView({ behavior: 'smooth' });
        // }

//----------------------------------------------Make the second option later When in Main Script----------------------------------------------    



function getNextPageUrl(pageNumber) {
    console.log(`Searching for URL for page ${pageNumber}`);
    const pageLink = document.querySelector(`#image-container a[href*='/g/'][href*='/${pageNumber}/']`);
    console.log(`Found page link: ${pageLink}`);
    if (pageLink) {
      return pageLink.href;
    } else {
      console.log(`No URL found for page ${pageNumber}`);
      return null;
    }
  }




// Save the current position without checking for visibility// Save the current position based on the current page
async function saveCurrentPosition(mangaId) {
    const totalPages = document.querySelectorAll('.manga-page-container').length;
    const currentPage = getCurrentVisiblePage(); // Get the current page number from the URL

    // Log the total pages and current page for debugging
    console.log(`Total pages loaded: ${totalPages}, trying to save position for page: ${currentPage}`);

    // Always save the position
    if (!isRestoringPosition) { // Only save if we are not restoring
        await storeData(mangaId, currentPage);
        console.log(`Position saved: Manga ID: ${mangaId}, Page: ${currentPage}`);
    } else {
        console.log(`Not saving position for Manga ID: ${mangaId} as we are restoring.`);
    }
}


// Periodically clean up storage
    manageStorage();


    window.loadMangaButton = document.createElement('button');
    loadMangaButton.textContent = 'Load Manga';
    loadMangaButton.className = 'load-manga-btn';
    loadMangaButton.style.position = 'fixed';
    loadMangaButton.style.bottom = '0';
    loadMangaButton.style.right = '0';
    loadMangaButton.style.padding = '5px';
    loadMangaButton.style.margin = '0 10px 10px 0';
    loadMangaButton.style.zIndex = '9999999999';

if (window.location.href.startsWith("https://nhentai.net/g/")) {
    const buttonsDiv = document.querySelectorAll('.buttons');

    if (buttonsDiv.length > 0) {
        //console.log('Buttons div already exists.');
    } else if (!document.body.contains(loadMangaButton)) {
        document.body.appendChild(loadMangaButton);

        loadMangaButton.addEventListener('click', async function () {
            const mangaId = extractMangaId(window.location.href);
            if (mangaId) {
                loadMangaImages(); // Load the manga images first

                // Check if there's a saved position for the manga
                const savedPosition = await retrieveData(mangaId);
                if (savedPosition) {
                    const savedPage = savedPosition.pageNum;
                    if (savedPage && (savedPage === totalPages || savedPage + 1 === totalPages)) {
                        await GM.deleteValue(mangaId);
                        console.log(`Saved position deleted for ${mangaId} since it's equal to total pages.`);
                    } else {
                        showPopupForSavedPosition("Do you want to load your last saved position?", async () => {
                            await loadSavedPosition(mangaId);
                        }, {
                            confirmText: 'Yes',
                            cancelText: 'No',
                            duration: 10000
                        });
                    }
                } else {
                    console.log('No saved position found for manga ID:', mangaId);
                }
            }
            loadMangaButton.remove();
        });
    }
}
    
    
        
    })();


//---------------------------**Continue Reading**---------------------------------

// Function to add the Continue Reading button to the menu
function addContinueReadingButton() {
    // Create the Continue Reading button
    const continueReadingButtonHtml = `
      <li>
        <a href="/continue_reading/">
          <i class="fa fa-arrow-right"></i>
          Continue Reading
        </a>
      </li>
    `;
    const continueReadingButton = $(continueReadingButtonHtml);

    // Append the Continue Reading button to the dropdown menu and the left menu
    const dropdownMenu = $('ul.dropdown-menu');
    dropdownMenu.append(continueReadingButton);

    const menu = $('ul.menu.left');
    menu.append(continueReadingButton);
}

// Call the function to add the Continue Reading button
addContinueReadingButton();

// Handle continue_reading page
if (window.location.href.includes('/continue_reading')) {
    console.log('Continue reading page detected');
    
    // Remove 404 Not Found elements
    const notFoundHeading = document.querySelector('h1');
    if (notFoundHeading && notFoundHeading.textContent === '404 – Not Found') {
        notFoundHeading.remove();
    }
    
    const notFoundMessage = document.querySelector('p');
    if (notFoundMessage && notFoundMessage.textContent === "Looks like what you're looking for isn't here.") {
        notFoundMessage.remove();
    }

    // Add custom CSS for better styling with dark theme
    const customCSS = document.createElement('style');
    customCSS.textContent = `
        body {
            background-color: #2c2c2c;
            color: #f1f1f1;
            margin: 0;
            padding: 0;
        }
        
        .continue-reading-container {
            width: 95%;
            max-width: 1200px;
            margin: 20px auto;
            font-family: Arial, sans-serif;
            padding: 20px;
            overflow-x: hidden;
            overflow-y: auto;
            /*max-height: 100vh; /* Prevents it from exceeding the screen height */
        }
        
        h1.continue-reading-title {
            text-align: center;
            color: #ed2553;
            margin-bottom: 20px;
        }
        
        table.manga-table {
            width: 100%;
            border-collapse: collapse;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
            background-color: #3c3c3c;
            border-radius: 5px;
            overflow: hidden;
            box-sizing: border-box;
            table-layout: fixed;
        }
        
        .manga-table th {
            background-color: #ed2553;
            color: white;
            padding: 12px;
            text-align: left;
        }
        
        .manga-table td {
            padding: 12px;
            border-bottom: 1px solid #4c4c4c;
            word-wrap: break-word;
            vertical-align: top;
        }
        
        /* Adjust column widths to optimize vertical layout */
        .manga-table th:nth-child(1),
        .manga-table td:nth-child(1) {
            width: 30%;
        }
        
        .manga-table th:nth-child(2),
        .manga-table td:nth-child(2) {
            width: 30%;
        }
        
        .manga-table th:nth-child(3),
        .manga-table td:nth-child(3) {
            width: 30%;
        }
        
        .manga-table th:nth-child(4),
        .manga-table td:nth-child(4) {
            width: 30%;
        }
        
        .manga-table th:nth-child(5),
        .manga-table td:nth-child(5) {
            width: 30%;
            text-align: center;
        }
        
        .manga-table tr:hover {
            background-color: #4c4c4c;
        }
        
        .manga-table a {
            color: #ed2553;
            text-decoration: none;
            font-weight: bold;
        }
        
        .manga-table a:hover {
            text-decoration: underline;
        }
        
        .manga-title {
            display: flex;
            align-items: flex-start;
            flex-wrap: wrap;
        }
        
        .manga-cover {
            width: 50px;
            height: 70px;
            margin-right: 10px;
            margin-bottom: 5px;
            object-fit: cover;
            border-radius: 3px;
        }
        
        .manga-title a {
            display: inline-block;
            /* Allow title to wrap properly */
            word-break: break-word;
            overflow-wrap: break-word;
            width: 100%;
            max-height: 100px;
            overflow: hidden;
            text-overflow: ellipsis;
            display: -webkit-box;
            -webkit-line-clamp: 4; /* Number of lines to show */
            -webkit-box-orient: vertical;
        }
        
        .progress-bar-container {
            width: 100%;
            background-color: #555;
            height: 8px;
            border-radius: 4px;
            margin-top: 5px;
        }
        
        .progress-bar {
            height: 100%;
            border-radius: 4px;
            background-color: #ed2553;
        }
        
        .language-tag {
            display: inline-block;
            padding: 3px 8px;
            border-radius: 3px;
            background-color: #555;
            font-size: 12px;
            text-transform: capitalize;
        }
        
        .continue-button {
            display: inline-block;
            padding: 6px 12px;
            background-color: #ed2553;
            color: white !important;
            border-radius: 4px;
            text-align: center;
            transition: background-color 0.2s;
        }
        
        .continue-button:hover {
            background-color: #c91c45;
            text-decoration: none !important;
        }
        
        .loading-indicator {
            text-align: center;
            margin: 20px 0;
            font-size: 16px;
            color: #ed2553;
        }
        
        .img-error {
            border: 2px solid #ed2553;
            position: relative;
        }
        
        .remove-button {
            display: flex;
            align-items: center;
            justify-content: center;
            background-color: white;
            color: #ed2553; /* Initial red icon */
            border: 2px solid #ed2553; /* Red border */
            border-radius: 50%;
            width: 30px;
            height: 30px;
            cursor: pointer;
            transition: background-color 0.2s, color 0.2s, transform 0.1s, box-shadow 0.2s;
            font-size: 14px;
            font-weight: bold;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
            margin: 0 auto;
        }

        .remove-button:hover {
            background-color: #ed2553; /* Turns red */
            color: white; /* Icon turns white */
            box-shadow: 0 3px 8px rgba(0, 0, 0, 0.3);
            transform: scale(1.1);
        }

        .remove-button:active {
            transform: scale(0.95);
            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
        }
        
        /* Responsive adjustments for smaller screens */
        @media (max-width: 768px) {
            .continue-reading-container {
                width: 98%;
                padding: 10px;
            }
            
            .manga-table th, 
            .manga-table td {
                padding: 8px 6px;
            }
            
            .manga-cover {
                width: 40px;
                height: 56px;
            }
            
            .continue-button {
                padding: 4px 8px;
                font-size: 14px;
            }
            
            /* For the continue button column */
            .manga-table td:nth-child(4) {
                position: relative;
                vertical-align: middle;
            }
            
            /* Align the continue button to the bottom */
            .manga-table td:nth-child(4) .continue-button {
                position: relative;
                bottom: 8px;
                left: 6px;
            }
        }
    `;
    document.head.appendChild(customCSS);

    // Create container element
    const container = document.createElement('div');
    container.className = 'continue-reading-container';
    document.body.appendChild(container);
    
    // Add title
    const title = document.createElement('h1');
    title.className = 'continue-reading-title';
    title.textContent = 'Continue Reading';
    container.appendChild(title);
    
    // Add loading indicator
    const loadingIndicator = document.createElement('div');
    loadingIndicator.className = 'loading-indicator';
    loadingIndicator.textContent = 'Loading your manga collection...';
    container.appendChild(loadingIndicator);

    // Implement the continue reading page
    const mangaList = [];
    
    // Array of possible subdomains to try
    const subdomains = ['t3', 't', 't1', 't2', 't4', 't5', 't7'];
    
    // Helper function to check if an image URL exists
    function checkImageExists(url) {
        return new Promise((resolve, reject) => {
            const img = new Image();
            img.onload = () => resolve(true);
            img.onerror = () => resolve(false);
            img.src = url;
        });
    }

// Helper function to find a working image URL
async function findWorkingImageUrl(mediaId) {
    for (const subdomain of subdomains) {
        // Try both webp and png formats
        const webpUrl = `https://${subdomain}.nhentai.net/galleries/${mediaId}/cover.webp`;
        const pngUrl = `https://${subdomain}.nhentai.net/galleries/${mediaId}/cover.png`;
        const jpgUrl = `https://${subdomain}.nhentai.net/galleries/${mediaId}/cover.jpg`;

        console.log(`Trying cover image URL: ${webpUrl}`);
        const webpExists = await checkImageExists(webpUrl);
        if (webpExists) {
            console.log(`Found working URL: ${webpUrl}`);
            return webpUrl;
        }

        console.log(`Trying cover image URL: ${pngUrl}`);
        const pngExists = await checkImageExists(pngUrl);
        if (pngExists) {
            console.log(`Found working URL: ${pngUrl}`);
            return pngUrl;
        }

        console.log(`Trying cover image URL: ${jpgUrl}`);
        const jpgExists = await checkImageExists(jpgUrl);
        if (jpgExists) {
            console.log(`Found working URL: ${jpgUrl}`);
            return jpgUrl;
        }
    }
    // If all fail, return the default with t3 subdomain as fallback
    return `https://t3.nhentai.net/galleries/${mediaId}/cover.jpg`;
}

// Function to create and display the table
function displayMangaTable() {
    if (mangaList.length === 0) {
        loadingIndicator.textContent = 'No manga found in your collection.';
        return;
    }
    
    // Sort the manga list by most recently read (highest page number to lowest)
    mangaList.sort((a, b) => b.currentPage - a.currentPage);
    console.log('Sorted manga list:', mangaList);

    // Create a table to display the manga list
    const table = document.createElement('table');
    table.className = 'manga-table';
    table.innerHTML = `
        <thead>
            <tr>
                <th>Manga Title</th>
                <th>Progress</th>
                <th>Language</th>
                <th>Action</th>
                <th>Remove</th>
            </tr>
        </thead>
        <tbody></tbody>
    `;
    
    const tbody = table.querySelector('tbody');

    // Add each manga to the table
    mangaList.forEach(manga => {
        console.log('Adding manga to table:', manga);
        const row = document.createElement('tr');
        
        // Calculate progress percentage
        const progressPercent = (manga.currentPage / manga.pages) * 100;
        
        row.innerHTML = `
            <td>
                <div class="manga-title">
                    <img class="manga-cover" src="${manga.coverImageUrl}" alt="Cover" onerror="this.classList.add('img-error')">
                    <a href="/g/${manga.id}/" title="${manga.title}">${manga.title}</a>
                </div>
            </td>
            <td>
                <div>Page ${manga.currentPage} of ${manga.pages}</div>
                <div class="progress-bar-container">
                    <div class="progress-bar" style="width: ${progressPercent}%"></div>
                </div>
            </td>
            <td><span class="language-tag">${manga.languageDisplay}</span></td>
            <td><a href="/g/${manga.id}/${manga.currentPage}/" class="continue-button" onclick="localStorage.setItem('redirected', 'true');">Continue Reading</a></td>            
            <td><button class="remove-button" data-id="${manga.id}">X</button></td>
        `;
        tbody.appendChild(row);

        // Remove manga entry from GM storage when 'X' button is clicked
        row.querySelector('.remove-button').addEventListener('click', async function() {
            const mangaId = this.getAttribute('data-id');
            console.log(`Removing manga ID: ${mangaId}`);

            // Remove from GM storage
            await GM.deleteValue(mangaId);
            await GM.deleteValue(`metadata_${mangaId}`);
            console.log(`Manga ID ${mangaId} removed from GM storage`);

            // Remove row from table
            row.remove();
        });
   

    // Remove loading indicator and add the table
    loadingIndicator.remove();
    container.appendChild(table);
    console.log('Table added to page');


            
            // Handle image loading errors and try alternative subdomains
            const imgElement = row.querySelector('.manga-cover');
            imgElement.addEventListener('error', async function() {
                console.log(`Image failed to load: ${manga.coverImageUrl}`);
                
                // Extract media ID from the URL
                const urlParts = manga.coverImageUrl.split('/');
                const mediaId = urlParts[urlParts.length - 2];
                
                // Find a working URL
                const newUrl = await findWorkingImageUrl(mediaId);
                
                if (newUrl !== manga.coverImageUrl) {
                    console.log(`Updating image URL from ${manga.coverImageUrl} to ${newUrl}`);
                    this.src = newUrl;
                    
                    // Update the cached metadata with the working URL
                    const metadataKey = `metadata_${manga.id}`;
                    GM.getValue(metadataKey, null).then(cachedMetadata => {
                        if (cachedMetadata) {
                            const metadata = JSON.parse(cachedMetadata);
                            metadata.coverImageUrl = newUrl;
                            GM.setValue(metadataKey, JSON.stringify(metadata))
                                .then(() => console.log(`Updated cached metadata with new URL for manga ID: ${manga.id}`));
                        }
                    });
                }
            });
        });

        // Remove loading indicator and add the table
        loadingIndicator.remove();
        container.appendChild(table);
        console.log('Table added to page');
    }
    
    // Function to fetch manga data from API and save it to GM.setValue
    async function fetchAndSaveMangaData(mangaId, pageNum) {
        const metadataKey = `metadata_${mangaId}`;
        
        // Try to get cached metadata first
        const cachedMetadata = await GM.getValue(metadataKey, null);
        
        if (cachedMetadata) {
            console.log(`Using cached metadata for manga ID: ${mangaId}`);
            const metadata = JSON.parse(cachedMetadata);
            
            mangaList.push({
                id: mangaId,
                title: metadata.title,
                coverImageUrl: metadata.coverImageUrl,
                languageDisplay: metadata.languageDisplay,
                pages: metadata.pages,
                currentPage: pageNum,
            });
            
            // Check if we have all manga data and display the table
            checkAndDisplayTable();
            return;
        }
        
        // If no cached data, fetch from API
        console.log(`Fetching metadata for manga ID: ${mangaId}`);
        try {
            const response = await fetch(`https://nhentai.net/api/gallery/${mangaId}`);
            const data = await response.json();
            
            if (data) {
                console.log('Fetched manga data:', data);
                const mangaTitle = data.title.english;
                const mediaId = data.media_id;
                
                // Get a working cover image URL with appropriate subdomain
                const coverImageUrl = await findWorkingImageUrl(mediaId);
                
                // Determine which language to display
                let languageDisplay = 'Unknown';
                const languages = data.tags.filter(tag => tag.type === 'language').map(tag => tag.name.toLowerCase());
                if (languages.includes('english')) {
                    languageDisplay = 'English';
                } else if (languages.includes('translated') && languages.length === 1) {
                    languageDisplay = 'English';
                } else if (languages.includes('translated') && languages.length > 1) {
                    // Exclude 'translated' and show other language(s)
                    const otherLanguages = languages.filter(lang => lang !== 'translated');
                    languageDisplay = otherLanguages.length > 0 ? otherLanguages.map(lang => lang.charAt(0).toUpperCase() + lang.slice(1)).join(', ') : 'Unknown';
                } else {
                    languageDisplay = languages.map(lang => lang.charAt(0).toUpperCase() + lang.slice(1)).join(', ');
                }
                
                const pages = data.num_pages;
                
                // Create metadata object to cache
                const metadata = {
                    title: mangaTitle,
                    coverImageUrl: coverImageUrl,
                    languageDisplay: languageDisplay,
                    pages: pages,
                    lastUpdated: Date.now()
                };
                
                // Save metadata to GM storage
                await GM.setValue(metadataKey, JSON.stringify(metadata));
                console.log(`Saved metadata for manga ID: ${mangaId}`);
                
                mangaList.push({
                    id: mangaId,
                    title: mangaTitle,
                    coverImageUrl: coverImageUrl,
                    languageDisplay: languageDisplay,
                    pages: pages,
                    currentPage: pageNum,
                });
                
                // Check if we have all manga data and display the table
                checkAndDisplayTable();
            } else {
                console.log('No data found for manga ID:', mangaId);
                checkAndDisplayTable();
            }
        } catch (error) {
            console.error('Error fetching manga data:', error);
            checkAndDisplayTable();
        }
    }
    
    // Counter to track pending fetch operations
    let pendingFetches = 0;
    let totalMangaCount = 0;
    
    // Function to check if all data is fetched and display the table
    function checkAndDisplayTable() {
        pendingFetches--;
        loadingIndicator.textContent = `Loading your manga collection... (${totalMangaCount - pendingFetches}/${totalMangaCount})`;
        
        if (pendingFetches <= 0) {
            displayMangaTable();
        }
    }


    // Function to delete completed manga
    async function deleteCompletedManga() {
        const allKeys = await GM.listValues();
        console.log('All keys:', allKeys);
        
        // Get all manga IDs (numerical keys)
        const mangaIds = allKeys.filter(key => key.match(/^\d+$/));
        
        // Process each manga
        for (const mangaId of mangaIds) {
            console.log('Processing manga ID:', mangaId);
            const mangaData = await GM.getValue(mangaId);
            const mangaDataObject = JSON.parse(mangaData);
            const pagesRead = mangaDataObject.pageNum;
            const metadataKey = `metadata_${mangaId}`;
            const metadata = await GM.getValue(metadataKey);
            const metadataObject = JSON.parse(metadata);
            const totalPages = metadataObject.pages;
            
            // Check if manga is completed (one less than or equal to total pages)
            if (pagesRead >= totalPages - 1) {
                console.log(`Deleting completed manga ID: ${mangaId}`);
                await GM.deleteValue(mangaId);
                await GM.deleteValue(metadataKey);
            }
        }
    }


    // Main function to load manga
    async function getStoredManga() {
        const mangaIds = [];
        const allValues = {};
        
        for (const key of await GM.listValues()) {
            const value = await GM.getValue(key);
            allValues[key] = value;
            
            if (key.match(/^\d+$/)) {
                mangaIds.push(key);
            }
        }
        
        console.log('All values:', allValues);
        totalMangaCount = mangaIds.length;
        pendingFetches = totalMangaCount;
        
        if (totalMangaCount === 0) {
            loadingIndicator.textContent = 'No manga found in your collection.';
            return;
        }
        
        // Process each manga
        mangaIds.forEach(mangaId => {
            console.log('Processing manga ID:', mangaId);
            const mangaData = JSON.parse(allValues[mangaId]);
            fetchAndSaveMangaData(mangaId, mangaData.pageNum);
        });
    }
     deleteCompletedManga();
    // Start the process
    getStoredManga();
}

//---------------------------**Continue Reading**---------------------------------