// ==UserScript==
// @name hltv.watchparty.layout
// @namespace http://tampermonkey.net/
// @version 2.0
// @description Clean up HLTV match pages, drag important parts, and apply a background gradient
// @author 2klex
// @match https://www.hltv.org/matches/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
// ❌ Elements to remove
const selectorsToRemove = [
'body > div.bgPadding > div.widthControl > div:nth-child(3) > div.right2Colbody > div.bgPadding > div.widthControl > div:nth-child(3) > div.right2Col',
'body > div.bgPadding > div.widthControl > div.main-top.centered-placement.gtSmartphone-only',
'body > div.bgPadding > div.widthControl > div:nth-child(2) > div.right2Col',
'body > div.bgPadding > div.widthControl > div.logoCon > div.BZ4Bl4KkTN > a > img.night-only',
'body > div.bgPadding > div.widthControl > div:nth-child(2) > div.contentCol > div.match-page > div.g-grid.maps > div.col-6.col-5-small',
'body > div.bgPadding > div.widthControl > div.logoCon > div.center-container',
'body > div.bgPadding > div.widthControl > div:nth-child(2) > div.leftCol',
'body > div.bgPadding > div.widthControl > div:nth-child(2) > div > div.match-page > div.stream-thumb-container',
'body > div.bgPadding > div.widthControl > div:nth-child(2) > div > div.match-page > div.head-to-head',
'body > div.bgPadding > div.widthControl > div:nth-child(2) > div > div.match-page > div.vrs-forecast-container.standard-box.text-ellipsis',
'body > div.bgPadding > div.widthControl > div:nth-child(2) > div > div.match-comments',
'#betting',
'body > div.bgPadding > div.widthControl > div:nth-child(2) > div > div.match-page > div:nth-child(11) > div',
// mapstats
'body > div.bgPadding > div.widthControl > div:nth-child(2) > div > div.match-page > div.map-stats-infobox > div.map-stats-infobox-right > div.map-stats-infobox-header',
'body > div.bgPadding > div.widthControl > div:nth-child(2) > div > div.match-page > div.map-stats-infobox > div.map-stats-infobox-right > div:nth-child(2)',
'body > div.bgPadding > div.widthControl > div:nth-child(2) > div > div.match-page > div.map-stats-infobox > div.map-stats-infobox-right > div:nth-child(3)',
'body > div.bgPadding > div.widthControl > div:nth-child(2) > div > div.match-page > div.map-stats-infobox > div.map-stats-infobox-right > div:nth-child(4)',
'body > div.bgPadding > div.widthControl > div:nth-child(2) > div > div.match-page > div.map-stats-infobox',
'body > div.bgPadding > div.widthControl > div:nth-child(2) > div > div.match-page > div.past-matches.spoiler',
'body > div.bgPadding > div.widthControl > div:nth-child(2) > div > div.match-page > div.standard-box.teamsBox',
'body > div.bgPadding > div.streams-section.gtSmartphone-only',
'body > footer',
'body > div.bgPadding > div.bg-sidebar.left.narrow.sticky-offset',
'body > div.bgPadding > div.bg-sidebar.right.narrow.sticky-offset',
'body > div.bgPadding > div.bg-sidebar.left.wide.sticky-offset',
'body > div.bgPadding > div.bg-sidebar.right.wide.sticky-offset',
'#map-stats',
'body > div.bgPadding > div.widthControl > div:nth-child(2) > div > div.match-page > span:nth-child(19)',
'body > div.bgPadding > div.widthControl > div:nth-child(2) > div > div.match-page > span:nth-child(22)',
'body > div.bgPadding > div.widthControl > div:nth-child(2) > div > div.match-page > span:nth-child(18)',
'body > div.bgPadding > div.widthControl > div:nth-child(2) > div > div.match-page > span:nth-child(21)',
'body > div.bgPadding > div.widthControl > div:nth-child(2) > div > div.match-page > span',
'#navBarContainerFull',
'#scoreboardElement > div > div:nth-child(6)',
//'#scoreboardElement > div > span',
//'#scoreboardElement > div > div.hide-minimap-toggle.hide-minimap-disabled',
'body > div.bgPadding > div.widthControl > div:nth-child(2) > div > div.match-page > div.standard-box.head-to-head-listing.spoiler',
'#aniplayer_AV63497311a7f92432c14aa7c3-1750497844917 > avp-player-ui',
'body > div.bgPadding > div.widthControl > div:nth-child(3) > div.contentCol > div.match-page > div.centered-placement.gtSmartphone-only > div > div',
'body > div.bgPadding > div.widthControl > div:nth-child(3) > div.contentCol > div.match-page > div:nth-child(12) > div > a > img.night-only',
'body > div.bgPadding > div.widthControl > div:nth-child(2) > div > div.match-page > div:nth-child(9) > div',
'#cto_banner_content',
'body > div.bgPadding > div.bg-sidebar.right.wide',
'body > div.bgPadding > div.bg-sidebar.left.wide'
];
// ✅ Elements to preserve from removal
const selectorsToPreserve = [
'#lineups',
'.keep-this',
// Add more here
];
// 🔄 Elements to wrap into draggable containers
const selectorsToWrap = [
'body > div.bgPadding > div.widthControl > div:nth-child(2) > div > div.match-page > div.g-grid.maps > div',
'#scoreboardElement > div > div.scoreboard',
'#lineups',
'#scoreboardElement > div > div:nth-child(5)',
'#match-stats#match-stats',
'body > div.bgPadding > div.widthControl > div.logoCon',
// Add more selectors to wrap here
];
// 🚫 Don't remove preserved elements
function isPreserved(element) {
return selectorsToPreserve.some(preserveSelector =>
element.matches(preserveSelector) || element.closest(preserveSelector)
);
}
// ❌ Remove elements unless preserved
function removeElements() {
selectorsToRemove.forEach(selector => {
document.querySelectorAll(selector).forEach(el => {
if (!isPreserved(el)) {
el.remove();
}
});
});
}
function setBackground() {
const gradient = 'linear-gradient(to bottom, #333333 0%, #0cad9d 100%)';
function applyBackground() {
if (document.getElementById('custom-gradient-bg')) return; // avoid duplicates
// Add a full-screen background gradient layer
const bgLayer = document.createElement('div');
bgLayer.id = 'custom-gradient-bg';
Object.assign(bgLayer.style, {
position: 'fixed',
top: '0',
left: '0',
width: '100vw',
height: '100vh',
backgroundImage: gradient,
backgroundAttachment: 'fixed',
backgroundRepeat: 'no-repeat',
backgroundSize: 'cover',
zIndex: '-9999',
pointerEvents: 'none'
});
document.body.insertAdjacentElement('afterbegin', bgLayer);
// Inject strong CSS overrides for body/html
const style = document.createElement('style');
style.textContent = `
html, body {
background: transparent !important;
background-image: none !important;
background-color: transparent !important;
}
.bgPadding {
background: transparent !important;
background-color: transparent !important;
}
.hl-ui-overlay {
position: fixed;
top: 10px;
right: 10px;
z-index: 100000;
background: #222;
color: white;
padding: 8px;
border-radius: 6px;
font-family: sans-serif;
}
.hl-ui-overlay button {
margin: 3px;
}
.hl-highlighted {
outline: 2px solid yellow;
cursor: move;
}
`;
document.head.appendChild(style);
}
if (document.readyState === 'complete') {
applyBackground();
} else {
window.addEventListener('load', applyBackground);
}
}
// 📦 Wrap and drag selected elements
function wrapElements() {
selectorsToWrap.forEach((selector, index) => {
document.querySelectorAll(selector).forEach((el, idx) => {
if (el.closest('.wrapped-draggable')) return;
const wrapper = document.createElement('div');
wrapper.className = 'wrapped-draggable';
const id = `wrapped-${index}-${idx}`;
Object.assign(wrapper.style, {
position: 'fixed',
left: el.getBoundingClientRect().left + window.scrollX + 'px',
top: el.getBoundingClientRect().top + window.scrollY + 'px',
zIndex: '10000',
background: '#00000',
color: '#fff',
width: el.offsetWidth + 'px',
height: el.offsetHeight + 'px',
resize: 'none',
boxSizing: 'border-box'
});
el.parentElement.insertBefore(wrapper, el);
wrapper.appendChild(el);
restorePosition(id, wrapper);
makeDraggable(wrapper, id);
});
});
}
// 🧲 Drag + Save
function makeDraggable(el, id) {
el.style.cursor = 'move';
el.addEventListener('mousedown', function (e) {
e.preventDefault();
let shiftX = e.clientX - el.getBoundingClientRect().left;
let shiftY = e.clientY - el.getBoundingClientRect().top;
function moveAt(pageX, pageY) {
el.style.left = (pageX - shiftX) + 'px';
el.style.top = (pageY - shiftY) + 'px';
}
function onMouseMove(e) {
moveAt(e.pageX, e.pageY);
}
document.addEventListener('mousemove', onMouseMove);
el.onmouseup = function () {
document.removeEventListener('mousemove', onMouseMove);
el.onmouseup = null;
savePosition(id, el);
};
});
el.ondragstart = () => false;
}
function savePosition(id, el) {
localStorage.setItem('wrap-pos-' + id, JSON.stringify({
left: el.style.left,
top: el.style.top
}));
}
function restorePosition(id, el) {
const saved = localStorage.getItem('wrap-pos-' + id);
if (saved) {
try {
const pos = JSON.parse(saved);
el.style.left = pos.left;
el.style.top = pos.top;
} catch (e) {
console.warn('Failed to restore wrapper position:', id);
}
}
}
function loadLayoutFromJson() {
// Paste your exported layout here
const layoutJson = {
"wrap-pos-wrapped-5-0": "{\"left\":\"20.918px\",\"top\":\"68.8398px\"}",
"wrap-pos-wrapped-0-0": "{\"left\":\"5.39931px\",\"top\":\"131.875px\"}",
"wrap-pos-wrapped-4-0": "{\"left\":\"2149.88px\",\"top\":\"332.875px\"}",
"wrap-pos-wrapped-2-0": "{\"left\":\"1182.38px\",\"top\":\"39.1562px\"}",
"wrap-pos-wrapped-1-0": "{\"left\":\"398.344px\",\"top\":\"37.875px\"}"
// Add more if needed
};
for (const [key, value] of Object.entries(layoutJson)) {
localStorage.setItem(key, value);
console.log('[LAYOUT LOADED]', key, value);
}
}
// 🔁 Run on load
function runAll() {
loadLayoutFromJson();
setBackground();
removeElements();
wrapElements();
}
window.addEventListener('load', runAll);
// 🔍 Dynamic DOM handling
const observer = new MutationObserver(runAll);
observer.observe(document.body, { childList: true, subtree: true });
})();