Hide all ESPN tennis matches that don't include favorite players or are empty, with an auto-hiding toggle button that remembers its state and sits beside the ESPN nav menu.
// ==UserScript==
// @name ESPN Tennis — Favorites Filter (With Persistent Button Placement)
// @namespace http://tampermonkey.net/
// @version 2.6
// @description Hide all ESPN tennis matches that don't include favorite players or are empty, with an auto-hiding toggle button that remembers its state and sits beside the ESPN nav menu.
// @match https://www.espn.com/tennis/scoreboard/*
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
const favoritePlayers = [
'Carlos Alcaraz', 'Jannik Sinner', 'Taylor Fritz','Novak Djokovic',
'Ben Shelton', 'Tommy Paul', 'Frances Tiafoe', 'Brandon Nakashima', 'Learner Tien'
];
const selectors = [
'.CompetitionsWrapper > div',
];
// --- Persistence ---
const STORAGE_KEY = 'espnTennisFavoritesShowAll';
let showAll = localStorage.getItem(STORAGE_KEY) === 'true';
// --- Core logic ---
const cleanText = str => (str || '').replace(/\s+/g, ' ').trim().toLowerCase();
const hasFavoriteName = el => {
const text = cleanText(el.innerText);
return favoritePlayers.some(name => text.includes(name.toLowerCase()));
};
const isEmptyCard = el => {
const text = cleanText(el.innerText);
return !/[A-Za-z]/.test(text) || text.length < 15;
};
function filterMatches() {
selectors.forEach(sel => {
document.querySelectorAll(sel).forEach(card => {
if (showAll) {
card.style.display = '';
} else {
const hide = isEmptyCard(card) || !hasFavoriteName(card);
card.style.display = hide ? 'none' : '';
}
});
});
}
// --- Button Code ---
function createToggleButton() {
const btn = document.createElement('button');
btn.innerText = showAll ? 'Show Only Favorites' : 'Show All Matches';
btn.id = 'favToggleBtn';
Object.assign(btn.style, {
marginLeft: '10px',
background: '#222',
color: '#fff',
border: '1px solid #555',
borderRadius: '8px',
padding: '7px 12px',
cursor: 'pointer',
fontSize: '13px',
fontFamily: 'sans-serif',
opacity: '0.85',
transition: 'opacity 0.4s ease, transform 0.3s ease'
});
btn.addEventListener('mouseenter', () => (btn.style.opacity = '1'));
btn.addEventListener('mouseleave', () => (btn.style.opacity = '0.85'));
btn.addEventListener('click', () => {
showAll = !showAll;
localStorage.setItem(STORAGE_KEY, showAll);
btn.innerText = showAll ? 'Show Only Favorites' : 'Show All Matches';
filterMatches();
});
// Place the button next to the target nav link
function placeButton() {
const navTarget = document.querySelector('.Nav__AccessibleMenuItem_Wrapper.justify-between.relative.n7.items-center.flex.Nav__Secondary__Menu__Item > .ph3.items-center.flex.clr-gray-01.Nav__Secondary__Menu__Link.Button--unstyled.AnchorLink');
if (navTarget && !document.getElementById('favToggleBtn')) {
navTarget.parentNode.insertBefore(btn, navTarget.nextSibling);
}
}
placeButton();
// In case site loads content later:
const navObs = new MutationObserver(placeButton);
navObs.observe(document.body, { childList: true, subtree: true });
return btn;
}
// React dynamic updates
const observer = new MutationObserver(() => requestIdleCallback(filterMatches));
observer.observe(document.body, { childList: true, subtree: true });
// Repeat during load to catch late content
let tries = 0;
const interval = setInterval(() => {
filterMatches();
if (++tries > 25) clearInterval(interval);
}, 2000);
// Initial startup
setTimeout(() => {
createToggleButton();
filterMatches();
}, 2000);
})();