GeoPixels Guild Overhaul

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

目前為 2025-11-15 提交的版本,檢視 最新版本

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

You will need to install an extension such as Tampermonkey to install this script.

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         GeoPixels Guild Overhaul
// @namespace    http://tampermonkey.net/
// @version      1.1
// @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;
        }
    `;
    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();

            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);
            });
        }
    }

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

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

})();