Improved Chub.ai (Card Title Links, Styling, and Pagination)

Makes Chub.ai card titles into links (copying existing link), styles them, and adds pagination.

// ==UserScript==
// @name         Improved Chub.ai (Card Title Links, Styling, and Pagination)
// @namespace    http://tampermonkey.net/
// @version      1.8
// @description  Makes Chub.ai card titles into links (copying existing link), styles them, and adds pagination.
// @author       Marcal91
// @match        https://chub.ai/*
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    function createTitleLink(cardElement) {
        // 1. Find the title element (innermost span holding the text).
        const titleSpan = cardElement.querySelector('.ant-card-head-title .ant-row span span');
        if (!titleSpan) {
            // If the specific inner span is not found, it might have already been replaced by our link.
            // Check if the link already exists within the title container.
            const titleContainer = cardElement.querySelector('.ant-card-head-title .ant-row > span');
            if (titleContainer && titleContainer.querySelector('a.chub-card-title-link')) {
                return; // Already processed
            }
            if(!titleSpan) return; // If still not found, bail.
        }

        // 2. Get the existing card link (the parent <a> of the whole card).
        const cardLink = cardElement.closest('a');
        if (!cardLink) {
            console.error("Chub.ai: Could not find parent card link for title:", cardElement);
            return;
        }

        // 3. Get the URL from the existing card link.
        const cardURL = cardLink.href;

        // 4. Create the new <a> element for the title.
        const linkElement = document.createElement('a');
        linkElement.href = cardURL;
        linkElement.textContent = titleSpan.textContent.trim();
        linkElement.target = '_blank';
        linkElement.rel = 'noopener noreferrer';
        linkElement.classList.add('chub-card-title-link');

        // 5. Replace the title span with the link.
        if (titleSpan.parentNode) {
            titleSpan.parentNode.replaceChild(linkElement, titleSpan);
        }
    }

    function addPagination() {
        const buttonContainer = document.querySelector('.flex.justify-between.mt-4');
        if (!buttonContainer) return;

        const currentURL = new URL(window.location.href);
        let currentPage = parseInt(currentURL.searchParams.get('page') || '1', 10);

        const paginationContainer = document.createElement('div');
        paginationContainer.classList.add('pagination-container');

        function createPageButton(pageNumber, text) {
            const pageButton = document.createElement('button');
            pageButton.type = 'button';
            pageButton.textContent = text || pageNumber.toString();
            pageButton.classList.add('ant-btn', 'css-s6hibu', 'ant-btn-default', 'pagination-link');

            if (!text && pageNumber === currentPage) { // Only style numeric current page
                pageButton.classList.add('current-page');
            }

            pageButton.addEventListener('click', (event) => {
                event.preventDefault();
                const url = new URL(window.location.href);
                url.searchParams.set('page', pageNumber.toString());
                window.location.href = url.toString();
            });
            return pageButton;
        }

        const firstPageButton = createPageButton(1, 'First');
        paginationContainer.appendChild(firstPageButton);

        let startPage = Math.max(1, currentPage - 2);
        let endPage = startPage + 4;
        const assumedTotalPages = 1000;

        if (endPage > assumedTotalPages) {
            endPage = assumedTotalPages;
            startPage = Math.max(1, endPage - 4);
        }
        startPage = Math.max(1, startPage);


        for (let i = startPage; i <= endPage; i++) {
            const pageButton = createPageButton(i);
            paginationContainer.appendChild(pageButton);
        }

        const randomButton = document.createElement('button');
        randomButton.type = 'button';
        randomButton.textContent = 'Random';
        randomButton.classList.add('ant-btn', 'css-s6hibu', 'ant-btn-default', 'pagination-link');
        randomButton.addEventListener('click', (event) => {
            event.preventDefault();
            const randomPage = Math.floor(Math.random() * assumedTotalPages) + 1;
            const url = new URL(window.location.href);
            url.searchParams.set('page', randomPage.toString());
            window.location.href = url.toString();
        });
        paginationContainer.appendChild(randomButton);

        const existingPagination = buttonContainer.querySelector('.pagination-container');
        if (existingPagination) {
            buttonContainer.replaceChild(paginationContainer, existingPagination);
        } else {
            buttonContainer.appendChild(paginationContainer);
        }
    }

    const observer = new MutationObserver((mutations) => {
        let cardsChanged = false;
        let buttonsChanged = false;

        mutations.forEach((mutation) => {
            mutation.addedNodes.forEach((node) => {
                if (node.nodeType === Node.ELEMENT_NODE) {
                    if (node.matches('.ant-card.char-card-class') || node.querySelector('.ant-card.char-card-class')) {
                        cardsChanged = true;
                    }
                    if (node.matches('.flex.justify-between.mt-4') || node.querySelector('.flex.justify-between.mt-4')) {
                        buttonsChanged = true;
                    }
                }
            });
        });

        if (cardsChanged) {
            document.querySelectorAll('.ant-card.char-card-class').forEach(createTitleLink);
        }
        if (buttonsChanged) {
            addPagination();
        }
    });

    observer.observe(document.body, { childList: true, subtree: true });

    let lastUrl = window.location.href;
    const urlObserver = new MutationObserver(() => {
      if (window.location.href !== lastUrl) {
        lastUrl = window.location.href;
        setTimeout(addPagination, 100);
      }
    });
    urlObserver.observe(document, {childList: true, subtree: true});

    document.querySelectorAll('.ant-card.char-card-class').forEach(createTitleLink);
    addPagination();

    const styleElement = document.createElement('style');
    styleElement.textContent = `
        .chub-card-title-link {
            color: white; /* Unvisited */
        }
        .chub-card-title-link:visited {
            color: yellow; /* Visited */
        }
        .pagination-container {
            display: flex;
            align-items: center;
            gap: 0.5rem;
        }
        .pagination-container .pagination-link { /* Applied to all buttons in pagination */
            background-color: #141414 !important;
            border: 1px solid #424242 !important;
            color: rgba(242,228,214,0.85) !important;
            min-width: 32px;
            height: 32px;
            padding: 0px 15px !important;
            line-height: 1.5 !important;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            border-radius: 6px !important;
        }
        .pagination-container .pagination-link:not(.current-page):hover {
            background-color: #1f1f1f !important;
            border-color: #5852a5 !important;
            color: #5852a5 !important;
        }
        .pagination-container .pagination-link.current-page {
            background-color: #2e2b74 !important;
            border-color: #5852a5 !important;
            color: white !important;
            font-weight: bold;
        }

        /* --- Styles for bigger tag scrollbar --- */
        div.custom-scroll:hover::-webkit-scrollbar {
            height: 8px !important; /* Increase height from site's 2px to 8px */
            /* The site CSS already positions it with 'position: absolute; bottom: 0;' which is fine. */
        }

        div.custom-scroll:hover::-webkit-scrollbar-thumb {
            background-color: #888 !important; /* A more visible scrollbar thumb color */
            border-radius: 4px !important;   /* Rounded corners for the thumb */
        }

        /* Optional: Add a subtle track background for the scrollbar area when hovering the tag container */
        div.custom-scroll:hover::-webkit-scrollbar-track {
            background-color: rgba(50, 50, 50, 0.2) !important; /* Light grey track background */
            border-radius: 4px !important;
        }
    `;
    document.head.appendChild(styleElement);

})();