GeoPixels Guild Overhaul

Make the guild modal draggable and non-obscuring, similar to ghostImageModal

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         GeoPixels Guild Overhaul
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Make the guild modal draggable and non-obscuring, similar to ghostImageModal
// @author       ariapokoteng
// @match        *://geopixels.net/*
// @match        *://*.geopixels.net/*
// @grant        none
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=geopixels.net
// ==/UserScript==

(function() {
    'use strict';

    // Add CSS styles for dragging
    const style = document.createElement('style');
    style.textContent = `
        .guild-modal-header {
            touch-action: none !important;
            -webkit-user-select: none !important;
            user-select: none !important;
        }
        
        .guild-modal-header span {
            touch-action: none !important;
            -webkit-user-select: none !important;
            user-select: none !important;
            display: block;
            flex: 1;
            padding-right: 10px;
        }
        
        .draggable-panel {
            touch-action: none !important;
        }

        /* Guild message collapsible styling */
        .guild-message-section {
            border: 1px solid #e5e7eb;
            border-radius: 0.5rem;
            overflow: hidden;
        }

        .guild-message-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding: 0.75rem;
            background-color: #f9fafb;
            cursor: pointer;
            user-select: none;
        }

        .guild-message-header:hover {
            background-color: #f3f4f6;
        }

        .guild-message-toggle {
            display: inline-block;
            width: 20px;
            height: 20px;
            text-align: center;
            line-height: 20px;
            font-weight: bold;
            color: #6b7280;
            transition: transform 0.2s ease;
        }

        .guild-message-toggle.collapsed {
            transform: rotate(-90deg);
        }

        .guild-message-content {
            max-height: 500px;
            overflow: hidden;
            transition: max-height 0.3s ease, padding 0.3s ease;
            padding: 0.75rem;
        }

        .guild-message-content.collapsed {
            max-height: 0;
            padding: 0;
        }

        /* Responsive layout for guild info grid */
        @media (max-width: 1024px) {
            #infoTab .grid.grid-cols-1.lg\\:grid-cols-3 {
                grid-template-columns: 1fr !important;
            }

            #infoTab .lg\\:col-span-2 {
                grid-column: auto !important;
            }

            #infoTab .lg\\:col-span-1 {
                grid-column: auto !important;
                order: 1;
            }

            #infoTab > .grid {
                display: flex;
                flex-direction: column;
            }

            #guildMembersContainer {
                order: 1;
                margin-top: 2rem;
            }
        }

        /* Force members container to bottom when message is collapsed */
        #infoTab.message-collapsed > .grid {
            display: block;
        }

        #infoTab.message-collapsed #guildMembersContainer {
            margin-top: 1rem;
        }

        /* Find button visited state */
        .guild-find-btn.visited {
            background-color: #a855f7 !important;
        }

        .guild-find-btn.visited:hover {
            background-color: #9333ea !important;
        }
    `;
    document.head.appendChild(style);

    // Wait for the modal to exist
    function waitForElement(selector, timeout = 10000) {
        return new Promise((resolve, reject) => {
            const startTime = Date.now();
            const checkInterval = setInterval(() => {
                const element = document.querySelector(selector);
                if (element) {
                    clearInterval(checkInterval);
                    resolve(element);
                } else if (Date.now() - startTime > timeout) {
                    clearInterval(checkInterval);
                    reject(new Error(`Element ${selector} not found within ${timeout}ms`));
                }
            }, 100);
        });
    }

    // Transform the guild modal to be draggable and floating
    async function transformGuildModal() {
        try {
            await waitForElement('#myGuildModal', 10000);

            const modal = document.getElementById('myGuildModal');
            const panel = document.getElementById('myGuildPanel');

            if (!modal || !panel) {
                console.error('[Guild Modal] myGuildModal or myGuildPanel not found');
                return;
            }

            // Check if already transformed
            if (panel.classList.contains('draggable-panel')) {
                return;
            }

            // Update modal styles to remove the overlay and make it draggable
            modal.style.position = 'fixed';
            modal.style.inset = 'auto';
            modal.style.backgroundColor = 'transparent';
            modal.style.justifyContent = 'flex-start';
            modal.style.alignItems = 'flex-start';
            modal.style.padding = '0';
            modal.style.pointerEvents = 'none';

            // Update panel styles to be floating and draggable
            panel.style.position = 'fixed';
            panel.style.top = '100px';
            panel.style.left = 'calc(50% - 25rem)';
            panel.style.width = '50rem';
            panel.style.maxWidth = '90vw';
            panel.style.maxHeight = '85vh';
            panel.style.zIndex = '40';
            panel.style.cursor = 'default';
            panel.style.transform = 'none';
            panel.style.opacity = '1';
            panel.style.scale = '1';
            panel.style.pointerEvents = 'auto';
            panel.classList.add('draggable-panel');

            // Remove any existing header if present
            const existingHeader = panel.querySelector('.guild-modal-header');
            if (existingHeader) {
                existingHeader.remove();
            }

            // Create a draggable header with close button
            const headerBar = document.createElement('div');
            headerBar.className = 'guild-modal-header';
            headerBar.style.cssText = `
                position: absolute;
                top: 0;
                left: 0;
                right: 0;
                height: 40px;
                background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
                cursor: move;
                border-radius: 0.75rem 0.75rem 0 0;
                display: flex;
                align-items: center;
                justify-content: space-between;
                padding: 0 16px;
                color: white;
                font-weight: 600;
                user-select: none;
                z-index: 50;
                pointer-events: auto;
            `;
            
            const titleSpan = document.createElement('span');
            titleSpan.textContent = 'Guild Panel';
            titleSpan.style.cursor = 'move';
            
            const closeBtn = document.createElement('button');
            closeBtn.textContent = '✕';
            closeBtn.style.cssText = `
                background: none;
                border: none;
                color: white;
                font-size: 24px;
                cursor: pointer;
                padding: 0;
                margin: 0;
                display: flex;
                align-items: center;
                justify-content: center;
                width: 30px;
                height: 30px;
                border-radius: 4px;
                transition: background-color 0.2s;
            `;
            closeBtn.onmouseover = () => closeBtn.style.backgroundColor = 'rgba(255,255,255,0.2)';
            closeBtn.onmouseout = () => closeBtn.style.backgroundColor = 'transparent';
            closeBtn.onclick = (e) => {
                e.stopPropagation();
                if (window.toggleMyGuildModal) {
                    window.toggleMyGuildModal();
                }
            };
            
            headerBar.appendChild(titleSpan);
            headerBar.appendChild(closeBtn);

            // Create resize handle
            const resizeHandle = document.createElement('div');
            resizeHandle.className = 'guild-modal-resize';
            resizeHandle.style.cssText = `
                position: absolute;
                bottom: 0;
                right: 0;
                width: 20px;
                height: 20px;
                cursor: nwse-resize;
                background: linear-gradient(135deg, transparent 0%, #3b82f6 100%);
                border-radius: 0 0 0.75rem 0;
                z-index: 51;
                pointer-events: auto;
            `;

            // Adjust panel padding to account for header
            panel.style.paddingTop = '50px';

            // Insert header as first child of panel
            if (panel.firstChild) {
                panel.insertBefore(headerBar, panel.firstChild);
            } else {
                panel.appendChild(headerBar);
            }

            // Append resize handle
            panel.appendChild(resizeHandle);

            // Set up drag functionality
            setupDragHandling(panel, titleSpan);
            
            // Set up resize functionality
            setupResizeHandling(panel, resizeHandle);

            // Set up message collapsible
            setupMessageCollapsible();

            // Set up Find button tracking
            setupFindButtonTracking();

            console.log('[Guild Modal] Transformed to draggable floating panel');

        } catch (error) {
            console.error('[Guild Modal] Error transforming modal:', error);
        }
    }

    // Set up drag handling for the panel
    function setupDragHandling(panel, header) {
        let isDragging = false;
        let startX = 0;
        let startY = 0;
        let offsetX = 0;
        let offsetY = 0;

        const onMouseDown = (e) => {
            // Don't drag if clicking on resize handle or close button
            if (e.target.closest('.guild-modal-resize') || e.target.closest('button')) {
                return;
            }

            isDragging = true;
            startX = e.clientX;
            startY = e.clientY;

            const rect = panel.getBoundingClientRect();
            offsetX = rect.left;
            offsetY = rect.top;

            // Add dragging state to panel
            panel.style.userSelect = 'none';

            document.addEventListener('mousemove', onMouseMove, true);
            document.addEventListener('mouseup', onMouseUp, true);

            e.preventDefault();
            e.stopPropagation();
        };

        const onMouseMove = (e) => {
            if (!isDragging) return;

            const deltaX = e.clientX - startX;
            const deltaY = e.clientY - startY;

            const newX = offsetX + deltaX;
            const newY = offsetY + deltaY;

            // Constrain to viewport with some padding
            const maxX = window.innerWidth - 100;
            const maxY = window.innerHeight - 100;

            const constrainedX = Math.max(-panel.offsetWidth + 100, Math.min(newX, maxX));
            const constrainedY = Math.max(0, Math.min(newY, maxY));

            panel.style.left = constrainedX + 'px';
            panel.style.top = constrainedY + 'px';
            panel.style.transform = 'none';
            panel.style.position = 'fixed';

            e.preventDefault();
            e.stopPropagation();
        };

        const onMouseUp = (e) => {
            if (isDragging) {
                isDragging = false;
                panel.style.userSelect = 'auto';
                document.removeEventListener('mousemove', onMouseMove, true);
                document.removeEventListener('mouseup', onMouseUp, true);
                e.preventDefault();
                e.stopPropagation();
            }
        };

        header.addEventListener('mousedown', onMouseDown, false);
    }

    // Set up resize handling for the panel
    function setupResizeHandling(panel, resizeHandle) {
        let isResizing = false;
        let startX = 0;
        let startY = 0;
        let startWidth = 0;
        let startHeight = 0;

        const onMouseDown = (e) => {
            isResizing = true;
            startX = e.clientX;
            startY = e.clientY;
            startWidth = panel.offsetWidth;
            startHeight = panel.offsetHeight;

            document.addEventListener('mousemove', onMouseMove);
            document.addEventListener('mouseup', onMouseUp);

            e.preventDefault();
        };

        const onMouseMove = (e) => {
            if (!isResizing) return;

            const deltaX = e.clientX - startX;
            const deltaY = e.clientY - startY;

            const newWidth = Math.max(400, startWidth + deltaX);
            const newHeight = Math.max(300, startHeight + deltaY);

            panel.style.width = newWidth + 'px';
            panel.style.height = newHeight + 'px';
        };

        const onMouseUp = () => {
            isResizing = false;
            document.removeEventListener('mousemove', onMouseMove);
            document.removeEventListener('mouseup', onMouseUp);
        };

        resizeHandle.addEventListener('mousedown', onMouseDown);
    }

    // Make the message box collapsible
    function setupMessageCollapsible() {
        const infoTab = document.getElementById('infoTab');
        const messageDiv = infoTab?.querySelector('div:has(> h3:first-child):has(#guildInfoMessage)');
        
        if (!messageDiv) return;

        // Wrap the message in a collapsible section
        const messageSection = document.createElement('div');
        messageSection.className = 'guild-message-section';

        const messageHeader = document.createElement('div');
        messageHeader.className = 'guild-message-header';

        const toggleSpan = document.createElement('span');
        toggleSpan.className = 'guild-message-toggle';
        toggleSpan.textContent = '▼';

        const titleSpan = document.createElement('span');
        titleSpan.textContent = 'Message';
        titleSpan.style.fontWeight = '500';

        messageHeader.appendChild(toggleSpan);
        messageHeader.appendChild(titleSpan);

        const messageContent = document.createElement('div');
        messageContent.className = 'guild-message-content';

        // Move the message content into the collapsible
        const h3 = messageDiv.querySelector('h3');
        const p = messageDiv.querySelector('#guildInfoMessage');

        if (h3 && p) {
            messageContent.appendChild(h3.cloneNode(true));
            messageContent.appendChild(p.cloneNode(true));
            
            // Replace the original message div
            messageDiv.parentNode.replaceChild(messageSection, messageDiv);
            messageSection.appendChild(messageHeader);
            messageSection.appendChild(messageContent);

            // Set up toggle functionality
            let isCollapsed = false;

            messageHeader.addEventListener('click', () => {
                isCollapsed = !isCollapsed;
                toggleSpan.classList.toggle('collapsed', isCollapsed);
                messageContent.classList.toggle('collapsed', isCollapsed);

                // Add/remove class to info tab for layout adjustment
                infoTab.classList.toggle('message-collapsed', isCollapsed);
            });
        }
    }

    // Track Find button clicks and change color to purple
    function setupFindButtonTracking() {
        const infoTab = document.getElementById('infoTab');
        if (!infoTab) return;

        // Set up a MutationObserver to watch for dynamically added Find buttons
        const observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                if (mutation.addedNodes.length) {
                    mutation.addedNodes.forEach((node) => {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            const buttons = node.querySelectorAll('button');
                            buttons.forEach((btn) => {
                                if (btn.textContent.trim() === 'Find' && !btn.classList.contains('guild-find-btn')) {
                                    btn.classList.add('guild-find-btn');
                                    btn.addEventListener('click', (e) => {
                                        btn.classList.add('visited');
                                    });
                                }
                            });
                        }
                    });
                }
            });

            // Also scan existing Find buttons
            scanForFindButtons();
        });

        function scanForFindButtons() {
            const findButtons = infoTab.querySelectorAll('button:not(.guild-find-btn)');
            findButtons.forEach((btn) => {
                if (btn.textContent.trim() === 'Find') {
                    btn.classList.add('guild-find-btn');
                    btn.addEventListener('click', (e) => {
                        btn.classList.add('visited');
                    });
                }
            });
        }

        // Initial scan
        scanForFindButtons();

        // Observe for changes
        observer.observe(infoTab, {
            childList: true,
            subtree: true
        });
    }

    // Initialize when page loads
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', transformGuildModal);
    } else {
        transformGuildModal();
    }

    console.log('[Guild Modal] v1.1 - Loaded');

})();