Make the guild modal draggable and non-obscuring, similar to ghostImageModal
// ==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');
})();