GeoPixels Guild Panel Overhaul

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         GeoPixels Guild Panel Overhaul
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Make the guild modal draggable and non-obscuring, similar to ghostImageModal
// @author       ???
// @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;
        }

        /* 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;
            }
        }
    `;
    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);

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

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

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

})();