Code:
// ==UserScript==
// @name CivForum Attachment Image Zoom And Pin
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Zoom and pin image attachments on civforum.de when hovering over them.
// @author You
// @match https://www.civforum.de/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
let zoomTimeout = null;
let overlay = null;
let isPinned = false;
let pinnedImg = null;
let unpinButton = null;
let attachmentImages = Array.from(document.querySelectorAll('a[href^="attachment.php?attachmentid="] > img'));
document.addEventListener('mouseover', function (e) {
const target = e.target;
if (
target.tagName === 'IMG' &&
target.parentElement &&
target.parentElement.tagName === 'A' &&
target.parentElement.getAttribute('href')?.startsWith('attachment.php?attachmentid=') &&
target.getAttribute('src')?.startsWith('attachment.php?attachmentid=')
) {
if (document.getElementById('image-hover-overlay')) return;
const fullImageUrl = target.parentElement.href;
zoomTimeout = setTimeout(() => {
createOverlayImage(fullImageUrl);
}, 150);
target.addEventListener('mouseleave', function onLeave() {
clearTimeout(zoomTimeout);
zoomTimeout = null;
target.removeEventListener('mouseleave', onLeave);
});
}
});
document.addEventListener('keydown', function (e) {
if (!overlay || !pinnedImg) return;
if (e.key === 'Escape') {
isPinned = false;
removeOverlay();
}
if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') {
const currentSrc = overlay.querySelector('img').src;
const currentIndex = attachmentImages.findIndex(img => img.parentElement.href === currentSrc);
if (currentIndex !== -1) {
let newIndex;
if (e.key === 'ArrowRight') {
newIndex = (currentIndex + 1) % attachmentImages.length;
} else {
newIndex = (currentIndex - 1 + attachmentImages.length) % attachmentImages.length;
}
const newFullImageUrl = attachmentImages[newIndex].parentElement.href;
overlay.querySelector('img').src = newFullImageUrl;
}
}
});
function removeOverlay() {
if (isPinned) return;
if (overlay) {
overlay.style.opacity = '0';
setTimeout(() => {
clearTimeout(zoomTimeout);
zoomTimeout = null;
overlay.remove();
overlay = null;
unpinButton = null;
}, 150);
}
}
window.addEventListener('scroll', removeOverlay);
function createOverlayImage(src) {
overlay = document.createElement('div');
overlay.id = 'image-hover-overlay';
overlay.style.position = 'fixed';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100vw';
overlay.style.height = '100vh';
overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.15)';
overlay.style.display = 'flex';
overlay.style.alignItems = 'center';
overlay.style.justifyContent = 'center';
overlay.style.zIndex = '10000';
overlay.style.cursor = 'pointer';
overlay.style.opacity = '0';
overlay.style.transition = 'opacity 0.2s ease';
const imgContainer = document.createElement('div');
imgContainer.style.position = 'relative';
imgContainer.style.display = 'inline-block';
pinnedImg = document.createElement('img');
pinnedImg.src = src;
pinnedImg.style.maxWidth = '90vw';
pinnedImg.style.maxHeight = '90vh';
pinnedImg.style.border = '2px solid white';
pinnedImg.style.borderRadius = '10px';
pinnedImg.style.boxShadow = '0 0 20px rgba(0,0,0,0.7)';
imgContainer.appendChild(pinnedImg);
function createPinButton(corner, positionStyles, alignStyles) {
const btn = document.createElement('button');
btn.textContent = '📌';
btn.title = `Pin to ${corner}`;
btn.style.position = 'absolute';
btn.style.width = '24px';
btn.style.height = '24px';
btn.style.border = 'none';
btn.style.borderRadius = '50%';
btn.style.backgroundColor = 'rgba(255, 215, 0, 0.8)';
btn.style.color = 'black';
btn.style.cursor = 'pointer';
btn.style.fontSize = '16px';
btn.style.display = 'flex';
btn.style.alignItems = 'center';
btn.style.justifyContent = 'center';
btn.style.boxShadow = '0 0 5px rgba(0,0,0,0.5)';
btn.style.transition = 'background-color 0.3s ease';
btn.style.zIndex = '10001';
Object.assign(btn.style, positionStyles);
btn.addEventListener('click', (e) => {
e.stopPropagation();
isPinned = true;
pinnedImg.style.border = '3px solid rgba(255, 215, 0, 0.6)';
pinnedImg.style.maxWidth = '45vw';
pinnedImg.style.maxHeight = '45vh';
overlay.style.justifyContent = alignStyles.justifyContent;
overlay.style.alignItems = alignStyles.alignItems;
overlay.style.backgroundColor = 'transparent';
pinnedImg.style.margin = '1rem';
showUnpinButton();
});
imgContainer.appendChild(btn);
}
createPinButton('Top Left', { top: '10px', left: '10px' }, { justifyContent: 'flex-start', alignItems: 'flex-start' });
createPinButton('Top Right', { top: '10px', right: '10px' }, { justifyContent: 'flex-end', alignItems: 'flex-start' });
createPinButton('Bottom Left', { bottom: '10px', left: '10px' }, { justifyContent: 'flex-start', alignItems: 'flex-end' });
createPinButton('Bottom Right', { bottom: '10px', right: '10px' }, { justifyContent: 'flex-end', alignItems: 'flex-end' });
overlay.appendChild(imgContainer);
document.body.appendChild(overlay);
requestAnimationFrame(() => {
overlay.style.opacity = '1';
});
imgContainer.addEventListener('mouseleave', removeOverlay);
}
function showUnpinButton() {
if (!pinnedImg || unpinButton) return;
unpinButton = document.createElement('button');
unpinButton.textContent = '❌';
unpinButton.title = 'Unpin and Close';
unpinButton.style.position = 'absolute';
unpinButton.style.bottom = '10px';
unpinButton.style.left = '50%';
unpinButton.style.transform = 'translateX(-50%)';
unpinButton.style.width = '30px';
unpinButton.style.height = '30px';
unpinButton.style.border = 'none';
unpinButton.style.borderRadius = '50%';
unpinButton.style.backgroundColor = 'rgba(255, 50, 50, 0.8)';
unpinButton.style.color = 'white';
unpinButton.style.cursor = 'pointer';
unpinButton.style.fontSize = '18px';
unpinButton.style.display = 'flex';
unpinButton.style.alignItems = 'center';
unpinButton.style.justifyContent = 'center';
unpinButton.style.boxShadow = '0 0 5px rgba(0,0,0,0.5)';
unpinButton.style.zIndex = '10002';
unpinButton.addEventListener('click', (e) => {
e.stopPropagation();
isPinned = false;
if (overlay) {
overlay.style.opacity = '0';
setTimeout(() => {
overlay.remove();
overlay = null;
unpinButton = null;
}, 150);
}
});
pinnedImg.parentElement.appendChild(unpinButton);
}
})();