// ==UserScript==
// @name Scrollable Volume Control with Custom Collapse and Step Control
// @namespace Tampermonkey Scripts
// @match https://www.redgifs.com/watch/*
// @grant GM_registerMenuCommand
// @version 2.8
// @author Bunglover24
// @license GNU GPLv3
// @icon https://icons.duckduckgo.com/ip2/redgifs.com.ico
// @description 10/10/24
// ==/UserScript==
const isFirefox = typeof InstallTrigger !== 'undefined'; // Detect Firefox
// Default step size for volume control, saved in localStorage
let volumeStep = parseFloat(localStorage.getItem('volumeStep')) || 0.01;
// Helper to save square position to localStorage
function savePosition(x, y) {
localStorage.setItem('volumeControlPos', JSON.stringify({ x, y }));
}
// Helper to load saved square position
function loadPosition() {
const pos = JSON.parse(localStorage.getItem('volumeControlPos'));
return pos ? { x: pos.x, y: pos.y } : { x: window.innerWidth / 2, y: window.innerHeight / 2 };
}
// Create the volume control square
function createVolumeControlSquare(video) {
const controlSquare = document.createElement('div');
const { x, y } = loadPosition();
// Style for the square
Object.assign(controlSquare.style, {
position: 'fixed',
left: `${x}px`,
top: `${y}px`,
width: '8.5mm', // Size adjusted for 100% text
height: '8.5mm', // Size adjusted for 100% text
backgroundColor: 'rgba(23, 23, 23, 0.8)', // Slight transparency for the square
color: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '3px', // Slight corner rounding
cursor: 'pointer',
zIndex: 1000,
transition: 'all 0.3s ease',
});
let volume = localStorage.getItem('videoVolume') || 0.01; // Default to 1%
video.volume = volume;
// Volume text, centered and styled
const volumeText = document.createElement('div');
volumeText.textContent = `${Math.round(volume * 100)}%`;
Object.assign(volumeText.style, {
pointerEvents: 'none', // Prevent interaction
fontSize: '12px',
fontFamily: 'Tahoma, sans-serif',
color: 'rgba(255, 255, 255, 0.9)', // Slightly less transparent text
textShadow: '1px 1px 3px rgba(0, 0, 0, 0.5)', // Subtle shadow for contrast
zIndex: 2, // Ensure it's above other elements
});
controlSquare.appendChild(volumeText);
// Volume control via scroll, adjustable steps
controlSquare.addEventListener('wheel', (e) => {
e.preventDefault();
volume = Math.min(Math.max(parseFloat(volume) + (e.deltaY > 0 ? -volumeStep : volumeStep), 0), 1);
video.volume = volume;
localStorage.setItem('videoVolume', volume);
volumeText.textContent = `${Math.round(volume * 100)}%`;
if (isFirefox) {
video.muted = false; // Firefox-specific handling
}
});
// Create the drag bar, adjusted to 3 pixels tall
const dragBar = document.createElement('div');
Object.assign(dragBar.style, {
position: 'absolute',
top: '0px', // Positioned at the top
width: '100%',
height: '3px', // Adjusted height to 3 pixels
backgroundColor: 'rgba(150, 200, 60, 0.9)', // Dragbar color (#96C83C)
cursor: 'move',
zIndex: 1, // Keep it behind the volume text
});
controlSquare.appendChild(dragBar);
// Optimized drag functionality
let isDragging = false, offsetX = 0, offsetY = 0;
dragBar.addEventListener('mousedown', (e) => {
isDragging = true;
offsetX = e.clientX - controlSquare.getBoundingClientRect().left;
offsetY = e.clientY - controlSquare.getBoundingClientRect().top;
document.body.style.userSelect = 'none'; // Disable text selection while dragging
});
document.addEventListener('mousemove', (e) => {
if (isDragging) {
requestAnimationFrame(() => {
const x = Math.max(0, Math.min(e.clientX - offsetX, window.innerWidth - controlSquare.offsetWidth));
const y = Math.max(0, Math.min(e.clientY - offsetY, window.innerHeight - controlSquare.offsetHeight));
controlSquare.style.left = `${x}px`;
controlSquare.style.top = `${y}px`;
});
}
});
document.addEventListener('mouseup', () => {
if (isDragging) {
const x = controlSquare.getBoundingClientRect().left;
const y = controlSquare.getBoundingClientRect().top;
savePosition(x, y); // Save position on release
isDragging = false;
document.body.style.userSelect = ''; // Re-enable text selection
}
});
// Handle collapse/expand on double-click
let isCollapsed = false;
controlSquare.addEventListener('dblclick', (e) => {
if (e.target !== dragBar) {
if (!isCollapsed) {
controlSquare.style.width = '5mm'; // Collapsed size, 7mm larger than original
controlSquare.style.height = '5mm'; // Collapsed size, 7mm larger than original
controlSquare.style.backgroundColor = 'rgba(150, 200, 60, 0.9)'; // Match dragbar color (#96C83C)
volumeText.style.display = 'none'; // Hide volume text
dragBar.style.display = 'none'; // Hide drag bar
} else {
controlSquare.style.width = '8.5mm'; // Restore original size
controlSquare.style.height = '8.5mm'; // Restore original size
controlSquare.style.backgroundColor = 'rgba(23, 23, 23, 0.8)'; // Restore transparency
volumeText.style.display = 'block'; // Show volume text
dragBar.style.display = 'block'; // Show drag bar
}
isCollapsed = !isCollapsed;
}
});
// Click to uncollapse
controlSquare.addEventListener('click', () => {
if (isCollapsed) {
controlSquare.style.width = '8.5mm'; // Restore original size
controlSquare.style.height = '8.5mm'; // Restore original size
controlSquare.style.backgroundColor = 'rgba(23, 23, 23, 0.8)'; // Restore transparency
volumeText.style.display = 'block'; // Show volume text
dragBar.style.display = 'block'; // Show drag bar
isCollapsed = false;
}
});
// Add the control square to the page
document.body.appendChild(controlSquare);
}
// Wait for video element to load using MutationObserver
let observer = new MutationObserver((mutations) => {
for (let mutation of mutations) {
if (mutation.addedNodes.length) {
let element = document.querySelector('.GifPreview_isActive');
if (element) {
const video = document.querySelector('video');
if (video) {
createVolumeControlSquare(video);
observer.disconnect(); // Stop observing once square is added
return;
}
}
}
}
});
observer.observe(document, { childList: true, subtree: true });
// Register Tampermonkey menu commands
// Reset button to reset volume to 1%
GM_registerMenuCommand('Reset Volume to 1%', () => {
const videos = document.querySelectorAll('video');
videos.forEach((video) => {
video.volume = 0.01;
localStorage.setItem('videoVolume', 0.01);
});
alert('Volume has been reset to 1%.');
});
// Button to set volume step size
GM_registerMenuCommand('Set Volume Step Size', () => {
let step = prompt(`Current step size is ${volumeStep * 100}%. Enter new step size as a percentage:`, volumeStep * 100);
if (step !== null) {
step = parseFloat(step) / 100;
if (!isNaN(step) && step > 0 && step <= 1) {
volumeStep = step;
localStorage.setItem('volumeStep', volumeStep);
alert(`Volume step size set to ${volumeStep * 100}%`);
} else {
alert('Invalid input. Please enter a number between 0.01 and 100.');
}
}
});