// ==UserScript== // @name ylOppTactsPreview (Modified) // @namespace douglaskampl // @version 2.3 // @description Shows the latest tactics used by an opponent from the scheduled matches page // @author Douglas // @match https://www.managerzone.com/?p=match&sub=scheduled // @icon https://www.google.com/s2/favicons?sz=64&domain=managerzone.com // @grant GM_addStyle // @license MIT // @downloadURL none // ==/UserScript== GM_addStyle(` .fade-in { animation: fade-in 0.2s ease forwards; } .fade-out { animation: fade-out 0.2s ease forwards; } @keyframes fade-in { from {opacity:0; transform:translateY(-5px);} to {opacity:1; transform:translateY(0);} } @keyframes fade-out { from {opacity:1; transform:translateY(0);} to {opacity:0; transform:translateY(-5px);} } .magnifier-icon { cursor: pointer !important; font-size: 14px !important; margin-left: 5px !important; z-index: 100 !important; pointer-events: auto !important; color: #444; transition: transform 0.2s ease, opacity 0.2s ease; } .magnifier-icon:hover { transform: scale(1.2); opacity: 0.8; } .tactics-container { position: absolute; top: 150px; left: 50%; transform: translateX(-50%); background: #fafafa; border: 1px solid #ccc; border-radius: 6px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); width: 500px; max-height: 80vh; overflow: hidden; font-family: sans-serif; color: #333; font-size: 13px; display: flex; flex-direction: column; z-index: 9999; } .tactics-header { display: flex; align-items: center; justify-content: space-between; padding: 8px 10px; background: #cdd; border-bottom: 1px solid #ccc; font-size: 12px; } .tactics-header .match-info-text { margin: 0; font-weight: normal; font-size: 12px; color: #333; } .tactics-header .close-button { background: none; border: none; cursor: pointer; font-size: 14px; color: #333; line-height: 1; padding: 0; margin: 0; transition: transform 0.2s ease, color 0.2s ease; } .tactics-header .close-button:hover { transform: scale(1.1); color: #000; } .tactics-header .title-main { font-weight: 600; color: #333; margin-bottom: 2px; } .tactics-header .title-subtitle { font-size: 11px; color: #666; font-style: italic; } .tactics-list { padding: 10px; overflow-y: auto; flex: 1; display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; align-content: flex-start; background: #fff; } .tactic-item { background: #fff; border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); text-align: center; padding: 6px; font-size: 11px; display: flex; flex-direction: column; align-items: center; transition: transform 0.2s ease, box-shadow 0.2s ease; } .tactic-item:hover { transform: scale(1.03); box-shadow: 0 4px 8px rgba(0,0,0,0.15); } .tactic-item p { margin: 5px 0 0 0; color: #333; } .tactics-container canvas { border-radius: 4px; transition: transform 0.2s ease, box-shadow 0.2s ease; margin-bottom: 5px; background: #f9f9f9; border: 1px solid #ddd; } .tactics-container canvas:hover { transform: scale(1.05); box-shadow: 0 4px 8px rgba(0,0,0,0.15); } #match-type-modal { position: absolute; top: 180px; left: 50%; transform: translateX(-50%); background: #fafafa; border: 1px solid #ccc; border-radius: 6px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); padding: 15px; font-family: sans-serif; font-size: 13px; color: #333; z-index: 10000; width: 220px; } #match-type-modal label { display: block; margin-bottom: 8px; font-weight: bold; font-size: 13px; } #match-type-modal select { padding: 5px; font-size: 13px; width: 100%; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 4px; background: #fff; } #match-type-modal .btn-group { display: flex; gap: 8px; justify-content: flex-end; } #match-type-modal button { padding: 5px 12px; font-size: 12px; cursor: pointer; background: #e0e0e0; border: 1px solid #aaa; border-radius: 4px; transition: background 0.2s ease; } #match-type-modal button:hover { background: #d0d0d0; } `); (function () { "use strict"; const CONSTANTS = { MAX_TACTICS: 10, SELECTORS: { FIXTURES_LIST: '#fixtures-results-list-wrapper', STATS_XENTE: '#legendDiv', ELO_SCHEDULED: '#eloScheduledSelect', HOME_TEAM: '.home-team-column.flex-grow-1', SELECT_WRAPPER: 'dd.set-default-wrapper' }, MATCH_TYPES: ['u18', 'u21', 'u23', 'no_restriction'] }; let ourTeamName = null; let selectedMatchTypeG = ''; let currentTidValue = ''; let currentOpponent = ''; let lastMagnifierRect = null; const observer = new MutationObserver(() => { insertIconsAndListeners(); }); function startObserving() { const fixturesList = document.querySelector(CONSTANTS.SELECTORS.FIXTURES_LIST); if (fixturesList) { observer.observe(fixturesList, { childList: true, subtree: true }); } } async function fetchLatestTactics(tidValue, opponent, matchType) { selectedMatchTypeG = matchType; currentTidValue = tidValue; currentOpponent = opponent; try { const response = await fetch( "https://www.managerzone.com/ajax.php?p=matches&sub=list&sport=soccer", { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' }, body: `type=played&hidescore=false&tid1=${tidValue}&offset=&selectType=${matchType}&limit=default`, credentials: 'include' } ); if (!response.ok) throw new Error('Network response was not ok'); const data = await response.json(); processTacticsData(data); } catch (_error) {} } function processTacticsData(data) { const parser = new DOMParser(); const htmlDocument = parser.parseFromString(data.list, 'text/html'); const scoreShownLinks = htmlDocument.querySelectorAll('a.score-shown'); const container = createTacticsContainer(selectedMatchTypeG, currentOpponent); document.body.appendChild(container); const listWrapper = container.querySelector('.tactics-list'); if (scoreShownLinks.length === 0) { const message = document.createElement('div'); message.style.textAlign = 'center'; message.style.color = '#555'; message.style.fontSize = '12px'; message.style.padding = '10px'; message.textContent = "No recent tactics found for the selected match type. Your opponent clearly doesn't care."; listWrapper.appendChild(message); container.classList.add('fade-in'); return; } scoreShownLinks.forEach((link, index) => { if (index >= CONSTANTS.MAX_TACTICS) return; const dl = link.closest('dl'); const theScore = link.textContent.trim(); const homeTeamName = dl.querySelector('.home-team-column .full-name')?.textContent.trim() || 'Home'; const awayTeamName = dl.querySelector('.away-team-column .full-name')?.textContent.trim() || 'Away'; const homeTeamLink = dl.querySelector('.home-team-column a.clippable'); const awayTeamLink = dl.querySelector('.away-team-column a.clippable'); let homeTid = null, awayTid = null; if (homeTeamLink) { homeTid = new URLSearchParams(new URL(homeTeamLink.href, location.href).search).get('tid'); } if (awayTeamLink) { awayTid = new URLSearchParams(new URL(awayTeamLink.href, location.href).search).get('tid'); } let homeGoals = 0; let awayGoals = 0; if (theScore.includes('-')) { const parts = theScore.split('-').map(x => x.trim()); if (parts.length === 2) { homeGoals = parseInt(parts[0]) || 0; awayGoals = parseInt(parts[1]) || 0; } } const mid = extractMidFromUrl(link.href); const tacticUrl = `https://www.managerzone.com/dynimg/pitch.php?match_id=${mid}`; const resultUrl = `https://www.managerzone.com/?p=match&sub=result&mid=${mid}`; const opponentIsHome = (homeTid === currentTidValue); const canvas = createCanvasWithReplacedColors(tacticUrl, opponentIsHome); const item = document.createElement('div'); item.className = 'tactic-item'; let opponentGoals = opponentIsHome ? homeGoals : awayGoals; let otherGoals = opponentIsHome ? awayGoals : homeGoals; if (opponentGoals > otherGoals) { item.style.backgroundColor = '#daf8da'; } else if (opponentGoals < otherGoals) { item.style.backgroundColor = '#f8dada'; } else { item.style.backgroundColor = '#f0f0f0'; } const linkA = document.createElement('a'); linkA.href = resultUrl; linkA.target = '_blank'; linkA.className = 'tactic-link'; linkA.style.color = '#333'; linkA.style.textDecoration = 'none'; linkA.appendChild(canvas); const scoreP = document.createElement('p'); scoreP.textContent = `${homeTeamName} ${theScore} ${awayTeamName}`; linkA.appendChild(scoreP); item.appendChild(linkA); listWrapper.appendChild(item); }); container.classList.add('fade-in'); } function showMatchTypeModal(tidValue, opponent, event) { const existingModal = document.getElementById('match-type-modal'); if (existingModal) { fadeOutAndRemove(existingModal); } const modal = document.createElement('div'); modal.id = 'match-type-modal'; modal.classList.add('fade-in'); const label = document.createElement('label'); label.textContent = 'Select match type:'; modal.appendChild(label); const select = document.createElement('select'); CONSTANTS.MATCH_TYPES.forEach(type => { const option = document.createElement('option'); option.value = type; option.textContent = type.replace('_', ' ').toUpperCase(); select.appendChild(option); }); modal.appendChild(select); const btnGroup = document.createElement('div'); btnGroup.className = 'btn-group'; const okButton = document.createElement('button'); okButton.textContent = 'OK'; okButton.onclick = () => { fadeOutAndRemove(modal); fetchLatestTactics(tidValue, opponent, select.value); }; const cancelButton = document.createElement('button'); cancelButton.textContent = 'Cancel'; cancelButton.onclick = () => fadeOutAndRemove(modal); btnGroup.append(okButton, cancelButton); modal.appendChild(btnGroup); document.body.appendChild(modal); const rect = event.target.getBoundingClientRect(); lastMagnifierRect = { left: window.scrollX + rect.left, top: window.scrollY + rect.top, bottom: window.scrollY + rect.bottom, width: rect.width, height: rect.height }; modal.style.position = 'absolute'; modal.style.top = (lastMagnifierRect.bottom + 5) + 'px'; modal.style.left = (lastMagnifierRect.left) + 'px'; } function createTacticsContainer(matchType, opponent) { const existingContainer = document.getElementById('tactics-container'); if (existingContainer) { fadeOutAndRemove(existingContainer); } const container = document.createElement('div'); container.id = 'tactics-container'; container.className = 'tactics-container'; const header = document.createElement('div'); header.className = 'tactics-header'; const title = document.createElement('div'); title.className = 'match-info-text'; title.innerHTML = `