您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Tracks and displays gained experience in The West with activity details, including reset functionality and draggable window
当前为
// ==UserScript== // @name The West - Experience Record // @namespace http://tampermonkey.net/ // @version 5.4 // @description Tracks and displays gained experience in The West with activity details, including reset functionality and draggable window // @author DK, Shikokuchuo // @include https://*.the-west.*/game.php* // @grant none // ==/UserScript== (function() { 'use strict'; // Inicjalizacja zmiennych let totalExperience = JSON.parse(localStorage.getItem('totalExperience')) || 0; let experienceLog = JSON.parse(localStorage.getItem('experienceLog')) || []; let savedPosition = JSON.parse(localStorage.getItem('experienceTrackerPosition')) || { top: '50px', left: 'auto', right: '310px' }; let isCollectingHistory = false; let isPaused = false; let shouldCancel = false; let collectionStartTime = null; let processedPagesTime = []; let tempExpLog = []; let messageTimeout = null; let isTrackerVisible = JSON.parse(localStorage.getItem('experienceTrackerVisible')) !== false; // domyślnie widoczny // Funkcja do wyświetlania komunikatów function showError(message, duration = 5000) { if (messageTimeout) { clearTimeout(messageTimeout); messageTimeout = null; } const statusElement = document.querySelector('#collection-status'); if (statusElement) { statusElement.innerHTML = ` <div style=" background: rgba(0,0,0,0.7); padding: 10px; border-radius: 5px; margin-top: 10px; color: white; text-align: center; animation: fadeIn 0.3s ease-in-out; "> ${message} </div> `; if (duration > 0) { messageTimeout = setTimeout(() => { if (statusElement) { statusElement.innerHTML = ''; } messageTimeout = null; }, duration); } } else { console.log('Status message:', message); } } // Funkcja do pokazywania komunikatu o postępie function showProgress(message) { const statusElement = document.querySelector('#collection-status'); if (statusElement) { statusElement.innerHTML = ` <div style=" background: rgba(0,0,0,0.7); padding: 10px; border-radius: 5px; margin-top: 10px; color: white; text-align: center; "> ${message} </div> `; } else { console.log('Progress:', message); } } // Dodaj style CSS dla animacji const style = document.createElement('style'); style.textContent = ` @keyframes fadeIn { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } } `; document.head.appendChild(style); // Funkcja do aktualizacji statusu kolekcji function updateCollectionStatus(processed, total, foundEntries) { const statusElement = document.querySelector('#collection-status'); if (statusElement) { const percent = Math.round((processed / total) * 100); // Używamy tempExpLog zamiast experienceLog podczas zbierania danych const workEntries = tempExpLog.filter(e => e.type === 'work').length; const duelEntries = tempExpLog.filter(e => e.type === 'duel').length; const battleEntries = tempExpLog.filter(e => e.type === 'battle').length; // Dodajmy też szacowany pozostały czas const timeEstimate = calculateTimeEstimate(processed, total); const timeInfo = timeEstimate ? `<br>Szacowany pozostały czas: ${timeEstimate}` : ''; statusElement.innerHTML = ` <div style="background: rgba(0,0,0,0.7); padding: 10px; border-radius: 5px;"> <strong>Status pobierania:</strong><br> Postęp: ${processed}/${total} stron (${percent}%)<br> Znalezione wpisy z XP: ${foundEntries}<br> - Prace: ${workEntries} (${tempExpLog.filter(e => e.type === 'work').reduce((sum, e) => sum + e.amount, 0)} XP)<br> - Pojedynki: ${duelEntries} (${tempExpLog.filter(e => e.type === 'duel').reduce((sum, e) => sum + e.amount, 0)} XP)<br> - Bitwy fortowe: ${battleEntries} (${tempExpLog.filter(e => e.type === 'battle').reduce((sum, e) => sum + e.amount, 0)} XP)${timeInfo} <div class="progress-bar" style=" background: #444; height: 20px; border-radius: 10px; margin-top: 10px; overflow: hidden; "> <div style=" width: ${percent}%; background: #4CAF50; height: 100%; transition: width 0.3s ease; "></div> </div> </div> `; } } // Funkcja do aktualizacji statystyk XP function updateXPStats() { const workXP = experienceLog.reduce((sum, entry) => { if (entry.type === 'work') { return sum + entry.amount; } return sum; }, 0); const duelXP = experienceLog.reduce((sum, entry) => { if (entry.type === 'duel') { return sum + entry.amount; } return sum; }, 0); const battleXP = experienceLog.reduce((sum, entry) => { if (entry.type === 'battle') { return sum + entry.amount; } return sum; }, 0); const workXPElement = document.querySelector('#work-xp'); const duelXPElement = document.querySelector('#duel-xp'); const battleXPElement = document.querySelector('#battle-xp'); if (workXPElement) workXPElement.textContent = workXP + ' XP'; if (duelXPElement) duelXPElement.textContent = duelXP + ' XP'; if (battleXPElement) battleXPElement.textContent = battleXP + ' XP'; } // Funkcja do debugowania systemu raportów function debugReportSystem() { console.log('Debugowanie systemu raportów...'); // Sprawdź różne parametry zapytań const testQueries = [ { page: 1, folder: 'all' }, { page: 1, folder: 'all', offset: 0 }, { page: 1, folder: 'all', offset: 0, limit: 50 } ]; function makeTestQuery(params, index) { setTimeout(() => { Ajax.remoteCall('reports', 'get_reports', params, function(response) { console.log('Test query ' + (index + 1) + ':', params); console.log('Response:', response); if (response) { console.log('Total reports:', response.count); console.log('Reports per page:', response.reports ? response.reports.length : 0); console.log('Response structure:', Object.keys(response)); if (response.reports && response.reports.length > 0) { console.log('Sample report structure:', Object.keys(response.reports[0])); } } }); }, index * 1000); } testQueries.forEach(makeTestQuery); } function formatTimeRemaining(milliseconds) { if (!milliseconds || isNaN(milliseconds)) return "obliczanie..."; const seconds = milliseconds / 1000; if (seconds < 60) return Math.round(seconds) + ' sekund'; const minutes = Math.floor(seconds / 60); const remainingSeconds = Math.round(seconds % 60); return minutes + ' min ' + remainingSeconds + ' sek'; } function calculateTimeEstimate(processedPages, totalPages) { if (processedPages < 2) return null; const currentTime = Date.now(); const timeElapsed = currentTime - collectionStartTime; // Oblicz średni czas na stronę w milisekundach const avgTimePerPage = timeElapsed / processedPages; if (isNaN(avgTimePerPage)) return null; // Oblicz pozostały czas w milisekundach const remainingPages = totalPages - processedPages; const estimatedTimeRemaining = remainingPages * avgTimePerPage; return formatTimeRemaining(estimatedTimeRemaining); } // Funkcja do tłumaczenia nazw kategorii function getCategoryName(folder) { switch(folder) { case 'all': return 'wszystkich raportów'; case 'job': return 'raportów z prac'; case 'duel': return 'raportów z pojedynków'; case 'fortbattle': return 'raportów z bitew fortowych'; default: return folder; } } // Funkcja do zbierania historii doświadczenia function collectExperienceHistory(maxPages = null, folder = 'all', callback = null) { if (isCollectingHistory) { console.log('Pobieranie danych jest już w trakcie. Poczekaj na zakończenie.'); if (callback) callback(); return; } isCollectingHistory = true; isPaused = false; shouldCancel = false; collectionStartTime = Date.now(); processedPagesTime = []; tempExpLog = []; console.log(`Rozpoczynam zbieranie danych dla ${getCategoryName(folder)}`); // Zachowujemy istniejące wpisy z innych kategorii let existingEntries = []; if (folder !== 'all') { existingEntries = experienceLog.filter(entry => { const entryCategory = entry.type === 'work' ? 'job' : entry.type === 'duel' ? 'duel' : entry.type === 'battle' ? 'fortbattle' : 'other'; return entryCategory !== folder; }); } let processedPages = 0; let failedAttempts = 0; const MAX_RETRIES = 3; // Pokaż przyciski kontrolne i ukryj standardowe const controlButtons = document.querySelector('#collection-controls'); const standardButtons = document.querySelector('#standard-buttons'); if (controlButtons) { controlButtons.style.display = 'grid'; } if (standardButtons) { standardButtons.style.display = 'none'; } // Dostosowane opóźnienia const MIN_DELAY = 2000; // minimalne opóźnienie między zapytaniami (2 sekundy) const MAX_DELAY = 2200; // maksymalne opóźnienie między zapytaniami (2.2 sekundy) const RETRY_DELAY = 3000; // opóźnienie przy ponownej próbie po błędzie (3 sekundy) function getRandomDelay() { return Math.floor(Math.random() * (MAX_DELAY - MIN_DELAY + 1)) + MIN_DELAY; } function finishCollection(wasSuccessful = true) { console.log(`Kończę zbieranie danych dla ${getCategoryName(folder)}`); isCollectingHistory = false; isPaused = false; // Przywróć standardowe przyciski i ukryj kontrolne const controlButtons = document.querySelector('#collection-controls'); const standardButtons = document.querySelector('#standard-buttons'); if (controlButtons) { controlButtons.style.display = 'none'; } if (standardButtons) { standardButtons.style.display = 'grid'; } // Ukryj status pobierania const statusElement = document.querySelector('#collection-status'); if (statusElement) { statusElement.innerHTML = ''; } if (!wasSuccessful) { showError('Anulowano zbieranie danych.'); if (callback) callback(); return; } if (tempExpLog.length === 0 && existingEntries.length === 0) { showError('Nie znaleziono żadnych wpisów z doświadczeniem.'); if (callback) callback(); return; } // Łączymy nowe wpisy z zachowanymi wpisami z innych kategorii if (folder === 'all') { experienceLog = tempExpLog; } else { // Usuń stare wpisy z aktualnej kategorii const otherEntries = experienceLog.filter(entry => { const entryCategory = entry.type === 'work' ? 'job' : entry.type === 'duel' ? 'duel' : entry.type === 'battle' ? 'fortbattle' : 'other'; return entryCategory !== folder; }); experienceLog = [...tempExpLog, ...otherEntries]; } totalExperience = experienceLog.reduce((sum, entry) => sum + entry.amount, 0); localStorage.setItem('experienceLog', JSON.stringify(experienceLog)); localStorage.setItem('totalExperience', JSON.stringify(totalExperience)); updateDisplay(); updateXPStats(); const newEntriesCount = tempExpLog.length; showError(`Zakończono pobieranie ${getCategoryName(folder)}! ${folder === 'all' ? 'Znaleziono' : 'Dodano'} ${newEntriesCount} wpisów.`); if (callback) { console.log(`Wywołuję callback dla kategorii: ${getCategoryName(folder)}`); callback(); } } // Pobierz wszystkie raporty Ajax.remoteCall('reports', 'get_reports', { page: 1, folder: folder }, function(initialData) { console.log('Pobieranie wszystkich raportów...'); processedPages = 0; if (!initialData || initialData.error) { console.error('Błąd pobierania danych:', initialData); isCollectingHistory = false; alert('Nie udało się pobrać informacji o raportach. Spróbuj ponownie.'); return; } const totalPages = initialData.count; const pagesToProcess = maxPages ? Math.min(maxPages, totalPages) : totalPages; console.log(`Znaleziono ${totalPages} stron raportów`); function processPage(page) { if (shouldCancel) { finishCollection(false); return; } if (!isCollectingHistory || page > pagesToProcess) { console.log('Zakończono przetwarzanie raportów'); finishCollection(true); return; } if (isPaused) { setTimeout(() => processPage(page), 500); return; } console.log(`Pobieram stronę ${page}...`); Ajax.remoteCall('reports', 'get_reports', { page: page, folder: folder }, function(data) { if (!isCollectingHistory || shouldCancel) return; if (data && !data.error && data.reports && data.reports.length > 0) { failedAttempts = 0; console.log(`Znaleziono ${data.reports.length} raportów na stronie ${page}`); data.reports.forEach(report => { const expMatch = report.popupData.match(/experience.png[^>]*>(?:[^<]*<\/[^>]+>)*[^<]*<td>(\d+)<\/td>/) || report.popupData.match(/Doświadczenie<\/span>\s*<span[^>]*>(\d+)\s*punktów/); const exp = expMatch ? parseInt(expMatch[1]) : 0; if (exp > 0) { const reportType = report.title.includes('Raport dot. pracy') ? 'work' : report.title.includes('Pojedynek') ? 'duel' : (report.title.includes('Bitwa') || report.title.includes('Fort')) ? 'battle' : 'other'; console.log(`Znaleziono XP w raporcie:`, { title: report.title, exp: exp, type: reportType }); tempExpLog.push({ amount: exp, source: report.title, timestamp: report.date_received, page: page, type: reportType }); } }); processedPages++; updateCollectionStatus(processedPages, pagesToProcess, tempExpLog.length); setTimeout(() => processPage(page + 1), getRandomDelay()); } else { console.error(`Błąd pobierania strony ${page}:`, data); failedAttempts++; if (failedAttempts < MAX_RETRIES) { setTimeout(() => processPage(page), RETRY_DELAY); } else { console.error(`Nie udało się przetworzyć strony ${page} po ${MAX_RETRIES} próbach`); finishCollection(true); } } }); } processPage(1); }); } // Funkcja do aktualizacji wyświetlania function updateDisplay() { const totalExperienceElement = document.querySelector('#total-experience'); if (totalExperienceElement) { totalExperienceElement.textContent = totalExperience; } } // Funkcja do dodawania okna z doświadczeniem function addExperienceWindow() { const existingWindow = document.querySelector('#experience-tracker'); if (existingWindow) { existingWindow.style.display = isTrackerVisible ? 'block' : 'none'; return; } const trackerDiv = document.createElement('div'); trackerDiv.id = 'experience-tracker'; trackerDiv.style.position = 'fixed'; trackerDiv.style.display = isTrackerVisible ? 'block' : 'none'; // Wczytaj zapisaną pozycję const savedPosition = JSON.parse(localStorage.getItem('experienceTrackerPosition')) || { top: '50px', left: 'auto', right: '310px' }; // Zastosuj zapisaną pozycję trackerDiv.style.top = savedPosition.top; trackerDiv.style.left = savedPosition.left; trackerDiv.style.right = savedPosition.right; trackerDiv.style.width = '280px'; trackerDiv.style.padding = '15px'; trackerDiv.style.background = 'rgba(20, 20, 20, 0.95)'; trackerDiv.style.color = '#fff'; trackerDiv.style.border = '1px solid #444'; trackerDiv.style.borderRadius = '8px'; trackerDiv.style.zIndex = '1000'; trackerDiv.style.cursor = 'move'; trackerDiv.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.3)'; trackerDiv.style.fontFamily = 'Arial, sans-serif'; trackerDiv.innerHTML = ` <div style="margin-bottom: 15px; border-bottom: 1px solid #444; padding-bottom: 10px;"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;"> <h3 style="margin: 0; font-size: 16px; color: #FFD700;">Doświadczenie</h3> <span id="total-experience" style="font-size: 20px; font-weight: bold; color: #2196F3;">${totalExperience} XP</span> </div> <div style="display: flex; flex-direction: column; gap: 5px; margin-top: 8px; font-size: 13px;"> <div style="display: flex; justify-content: space-between; padding: 3px 0;"> <span style="color: #BBB;">Z prac:</span> <span id="work-xp" style="color: #27AE60;">0 XP</span> </div> <div style="display: flex; justify-content: space-between; padding: 3px 0;"> <span style="color: #BBB;">Z pojedynków:</span> <span id="duel-xp" style="color: #e74c3c;">0 XP</span> </div> <div style="display: flex; justify-content: space-between; padding: 3px 0;"> <span style="color: #BBB;">Z bitew fortowych:</span> <span id="battle-xp" style="color: #9b59b6;">0 XP</span> </div> </div> </div> <div id="collection-status" style="margin: 10px 0; font-size: 13px; color: #BBB;"></div> <div id="collection-controls" style="display: none; grid-template-columns: 1fr 1fr; gap: 8px; margin: 10px 0;"> <button id="pause-collection" style=" padding: 8px; background: #F39C12; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; transition: background 0.2s; ">Wstrzymaj</button> <button id="cancel-collection" style=" padding: 8px; background: #E74C3C; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; transition: background 0.2s; ">Anuluj</button> </div> <div id="standard-buttons" style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 15px;"> <button id="update-history" style=" padding: 8px; background: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; transition: background 0.2s; ">Aktualizuj dane</button> <button id="collect-history" style=" padding: 8px; background: #27AE60; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; transition: background 0.2s; ">Zbierz dane</button> <button id="show-experience-log" style=" padding: 8px; background: #2C3E50; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; transition: background 0.2s; ">Szczegóły</button> <button id="reset-experience" style=" padding: 8px; background: #C0392B; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; transition: background 0.2s; ">Kasuj</button> </div> `; // Dodanie hover efektów dla przycisków const buttons = trackerDiv.getElementsByTagName('button'); for (let button of buttons) { button.addEventListener('mouseover', function() { const currentBg = this.style.background; this.style.background = currentBg.replace(')', ' brightness(120%)'); }); button.addEventListener('mouseout', function() { const currentBg = this.style.background; this.style.background = currentBg.replace(' brightness(120%)', ''); }); } document.body.appendChild(trackerDiv); // Dodanie obsługi przycisków document.querySelector('#collect-history').addEventListener('click', () => { if (!isCollectingHistory) { showCategorySelectionPanel('collect'); } }); document.querySelector('#pause-collection').addEventListener('click', function() { isPaused = !isPaused; this.textContent = isPaused ? 'Wznów' : 'Wstrzymaj'; this.style.background = isPaused ? '#27AE60' : '#F39C12'; }); document.querySelector('#cancel-collection').addEventListener('click', function() { if (confirm('Czy na pewno chcesz anulować zbieranie danych?')) { shouldCancel = true; } }); document.querySelector('#show-experience-log').addEventListener('click', showExperienceDetails); document.querySelector('#reset-experience').addEventListener('click', resetExperience); // Dodaj obsługę przycisku aktualizacji document.querySelector('#update-history').addEventListener('click', () => { if (!isCollectingHistory) { showCategorySelectionPanel('update'); } }); makeDraggable(trackerDiv); // Aktualizuj statystyki przy tworzeniu okna updateXPStats(); // Modyfikacja funkcji addExperience aby aktualizowała statystyki const originalAddExperience = addExperience; addExperience = function(amount, source) { originalAddExperience.call(this, amount, source); updateXPStats(); }; // Dodanie aktualizacji statystyk po zebraniu danych const originalCollectExperienceHistory = collectExperienceHistory; collectExperienceHistory = function(maxPages, folder, callback = null) { originalCollectExperienceHistory.call(this, maxPages, folder, callback); setTimeout(updateXPStats, 1000); // Aktualizuj po zakończeniu zbierania }; } // Funkcja przeciągania elementu function makeDraggable(element) { let isDragging = false; let startX, startY, initialX, initialY; // Znajdujemy element nagłówka (cały górny obszar z doświadczeniem) const headerArea = element.querySelector('div:first-child'); if (headerArea) { headerArea.style.cursor = 'move'; // Zmiana kursora na "move" dla całego obszaru nagłówka } element.onmousedown = (event) => { // Sprawdzamy, czy kliknięcie było w obszarze nagłówka lub jego potomków let targetElement = event.target; let isInHeader = false; // Sprawdzamy, czy kliknięty element jest w obszarze nagłówka while (targetElement && targetElement !== element) { if (targetElement === headerArea) { isInHeader = true; break; } targetElement = targetElement.parentElement; } // Jeśli kliknięcie było w obszarze nagłówka i nie w przycisk if (isInHeader && event.target.tagName !== 'BUTTON') { isDragging = true; startX = event.clientX; startY = event.clientY; initialX = parseInt(window.getComputedStyle(element).left, 10) || 0; initialY = parseInt(window.getComputedStyle(element).top, 10) || 0; document.onmousemove = onMouseMove; document.onmouseup = onMouseUp; } }; function onMouseMove(event) { if (!isDragging) return; const deltaX = event.clientX - startX; const deltaY = event.clientY - startY; element.style.left = `${initialX + deltaX}px`; element.style.top = `${initialY + deltaY}px`; element.style.right = 'auto'; // Deaktywuje "right" gdy "left" jest ustawione } function onMouseUp() { if (isDragging) { // Zapisz pozycję w localStorage const position = { top: element.style.top, left: element.style.left, right: 'auto' }; localStorage.setItem('experienceTrackerPosition', JSON.stringify(position)); } isDragging = false; document.onmousemove = null; document.onmouseup = null; } } // Funkcja zapisywania pozycji okna function savePosition(element) { const top = element.style.top; const left = element.style.left; const right = element.style.right; localStorage.setItem('experienceTrackerPosition', JSON.stringify({ top, left, right })); alert('Pozycja okna została zapisana!'); } // Funkcja do zapisywania zdobytego doświadczenia function addExperience(amount, source) { totalExperience += amount; experienceLog.push({ amount, source, timestamp: new Date().toLocaleString() }); // Aktualizuj lokalne przechowywanie localStorage.setItem('totalExperience', JSON.stringify(totalExperience)); localStorage.setItem('experienceLog', JSON.stringify(experienceLog)); // Zaktualizuj wyświetlaną wartość const totalExperienceElement = document.querySelector('#total-experience'); if (totalExperienceElement) { totalExperienceElement.textContent = totalExperience; } } // Funkcja do pokazywania szczegółów doświadczenia function showExperienceDetails() { const logWindow = window.open('', 'Experience Log', 'width=800,height=800'); logWindow.document.write(` <!DOCTYPE html> <html> <head> <title>Historia doświadczenia</title> <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet"> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Roboto', sans-serif; } body { background: #f5f6fa; color: #2d3436; line-height: 1.6; padding: 30px; } .container { max-width: 1200px; margin: 0 auto; } .header { text-align: center; margin-bottom: 30px; color: #2d3436; } .header h1 { font-size: 28px; font-weight: 500; } .controls { background: white; padding: 20px; border-radius: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.05); margin-bottom: 30px; display: flex; gap: 15px; flex-wrap: wrap; align-items: center; } .controls select, .controls input { padding: 10px 15px; border: 1px solid #dfe6e9; border-radius: 8px; font-size: 14px; color: #2d3436; background: white; min-width: 160px; outline: none; transition: all 0.3s ease; } .controls select:hover, .controls input:hover { border-color: #74b9ff; } .controls select:focus, .controls input:focus { border-color: #0984e3; box-shadow: 0 0 0 3px rgba(9,132,227,0.1); } .controls button { padding: 10px 20px; background: #0984e3; color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: 500; transition: all 0.3s ease; } .controls button:hover { background: #0652DD; transform: translateY(-1px); } .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 30px; } .stat-card { background: white; padding: 20px; border-radius: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.05); transition: all 0.3s ease; } .stat-card:hover { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(0,0,0,0.1); } .stat-card h3 { color: #636e72; font-size: 14px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 10px; } .stat-card .value { font-size: 24px; font-weight: 700; color: #2d3436; margin-bottom: 5px; } .stat-card .sub-value { font-size: 14px; color: #636e72; } .entries-list { background: white; border-radius: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.05); overflow: hidden; } .entry { display: grid; grid-template-columns: auto 1fr auto; gap: 20px; padding: 15px 20px; align-items: center; border-bottom: 1px solid #f1f2f6; transition: all 0.2s ease; } .entry:hover { background: #f8f9fa; } .entry:last-child { border-bottom: none; } .entry .timestamp { color: #636e72; font-size: 14px; white-space: nowrap; } .entry .source { color: #2d3436; font-weight: 500; } .entry .amount { color: #00b894; font-weight: 700; font-size: 16px; } .badge { display: inline-block; padding: 4px 8px; border-radius: 6px; font-size: 12px; font-weight: 500; margin-right: 8px; } .badge-church { background: #81ecec; color: #00cec9; } .badge-work { background: #55efc4; color: #00b894; } .badge-duel-player { background: #ff7675; color: #d63031; } .badge-duel-bandit { background: #fab1a0; color: #e17055; } .badge-battle { background: #9b59b6; color: #8e44ad; } .badge-other { background: #81ecec; color: #00cec9; } .pagination { display: flex; justify-content: center; align-items: center; gap: 10px; margin-top: 30px; } .pagination button { padding: 8px 12px; border: 1px solid #dfe6e9; background: white; color: #2d3436; border-radius: 8px; cursor: pointer; transition: all 0.3s ease; } .pagination button:hover { border-color: #0984e3; color: #0984e3; } .pagination button.active { background: #0984e3; color: white; border-color: #0984e3; } .pagination .info { color: #636e72; font-size: 14px; } .empty-state { text-align: center; padding: 60px 20px; color: #636e72; } @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .entry { animation: fadeIn 0.3s ease-out forwards; } #error-message { position: fixed; top: 20px; right: 20px; background: #d63031; color: white; padding: 15px 20px; border-radius: 8px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); display: none; animation: fadeIn 0.3s ease-out; } </style> </head> <body> <div class="container"> <div class="header"> <h1>Historia doświadczenia</h1> </div> <div class="controls"> <select id="categoryFilter" onchange="applyFilters()"> <option value="all">Wszystkie źródła</option> <option value="Prace">Zwykłe prace</option> <option value="Kosciol">Budowa kościoła</option> <option value="PojedynkiGracze">Pojedynki z graczami</option> <option value="PojedynkiBandyci">Pojedynki z bandytami</option> <option value="Bitwy">Bitwy fortowe</option> <option value="Inne">Inne</option> </select> <select id="sortBy" onchange="applyFilters()"> <option value="newest">Najnowsze</option> <option value="exp-desc">Najwięcej XP</option> <option value="exp-asc">Najmniej XP</option> </select> <input type="text" id="searchInput" oninput="applyFilters()" placeholder="Szukaj..."> <button onclick="applyFilters()">Filtruj</button> </div> <div id="error-message"></div> <div id="summary" class="stats-grid"></div> <div id="experience-list" class="entries-list"></div> <div id="pagination" class="pagination"></div> </div> <script> let currentPage = 1; const itemsPerPage = 25; let filteredData = []; const originalData = ${JSON.stringify(experienceLog)}; function showError(message) { const errorDiv = document.getElementById('error-message'); errorDiv.textContent = message; errorDiv.style.display = 'block'; setTimeout(() => { errorDiv.style.display = 'none'; }, 5000); } function updateSummary() { try { const summary = { total: 0, categories: { 'Zwykłe prace': { exp: 0, count: 0 }, 'Budowa kościoła': { exp: 0, count: 0 }, 'Pojedynki z graczami': { exp: 0, count: 0 }, 'Pojedynki z bandytami': { exp: 0, count: 0 }, 'Bitwy fortowe': { exp: 0, count: 0 }, 'Inne': { exp: 0, count: 0 } } }; originalData.forEach(entry => { if (!entry || !entry.amount) return; summary.total += entry.amount; if (entry.type === 'work') { if (entry.source.includes('rozbudowa Kościół')) { summary.categories['Budowa kościoła'].exp += entry.amount; summary.categories['Budowa kościoła'].count++; } else { summary.categories['Zwykłe prace'].exp += entry.amount; summary.categories['Zwykłe prace'].count++; } } else if (entry.type === 'duel') { if (entry.source.includes('bandytą') || entry.source.includes('bandit')) { summary.categories['Pojedynki z bandytami'].exp += entry.amount; summary.categories['Pojedynki z bandytami'].count++; } else { summary.categories['Pojedynki z graczami'].exp += entry.amount; summary.categories['Pojedynki z graczami'].count++; } } else if (entry.type === 'battle') { summary.categories['Bitwy fortowe'].exp += entry.amount; summary.categories['Bitwy fortowe'].count++; } else { summary.categories['Inne'].exp += entry.amount; summary.categories['Inne'].count++; } }); const summaryDiv = document.getElementById('summary'); let summaryHTML = '<div class="stat-card">' + '<h3>Łącznie</h3>' + '<div class="value">' + summary.total.toLocaleString() + ' XP</div>' + '<div class="sub-value">' + originalData.length + ' wpisów</div>' + '</div>'; Object.entries(summary.categories).forEach(([category, data]) => { if (data.count > 0) { // Pokazuj tylko kategorie, które mają wpisy summaryHTML += '<div class="stat-card">' + '<h3>' + category + '</h3>' + '<div class="value">' + data.exp.toLocaleString() + ' XP</div>' + '<div class="sub-value">' + data.count + ' wpisów</div>' + '</div>'; } }); summaryDiv.innerHTML = summaryHTML; } catch (error) { showError('Błąd podczas generowania podsumowania: ' + error.message); } } function applyFilters() { try { const category = document.getElementById('categoryFilter').value; const sortBy = document.getElementById('sortBy').value; const searchText = document.getElementById('searchInput').value.toLowerCase(); filteredData = originalData.filter(entry => { if (!entry || !entry.source) return false; const matchesCategory = category === 'all' ? true : category === 'Kosciol' ? (entry.type === 'work' && entry.source.includes('rozbudowa Kościół')) : category === 'Prace' ? (entry.type === 'work' && !entry.source.includes('rozbudowa Kościół')) : category === 'PojedynkiGracze' ? (entry.type === 'duel' && !entry.source.includes('bandytą') && !entry.source.includes('bandit')) : category === 'PojedynkiBandyci' ? (entry.type === 'duel' && (entry.source.includes('bandytą') || entry.source.includes('bandit'))) : category === 'Bitwy' ? entry.type === 'battle' : category === 'Inne' ? (entry.type !== 'work' && entry.type !== 'duel' && entry.type !== 'battle') : false; const matchesSearch = searchText === '' || entry.source.toLowerCase().includes(searchText); return matchesCategory && matchesSearch; }); filteredData.sort((a, b) => { switch(sortBy) { case 'newest': // Poprawione sortowanie dat const dateA = new Date(a.timestamp.replace(/(\d{2})\.(\d{2})\.(\d{4})/, '$3-$2-$1')); const dateB = new Date(b.timestamp.replace(/(\d{2})\.(\d{2})\.(\d{4})/, '$3-$2-$1')); return dateB - dateA; case 'exp-desc': return b.amount - a.amount; case 'exp-asc': return a.amount - b.amount; default: return 0; } }); currentPage = 1; renderPage(); } catch (error) { showError('Błąd podczas filtrowania: ' + error.message); } } function renderPage() { try { const start = (currentPage - 1) * itemsPerPage; const end = start + itemsPerPage; const pageItems = filteredData.slice(start, end); const listDiv = document.getElementById('experience-list'); listDiv.innerHTML = ''; if (pageItems.length === 0) { listDiv.innerHTML = '<div class="empty-state">Brak wpisów spełniających kryteria</div>'; } else { pageItems.forEach(entry => { let typeBadge = ''; if (entry.type === 'work') { if (entry.source.includes('rozbudowa Kościół')) { typeBadge = '<span class="badge badge-church">Kościół</span>'; } else { typeBadge = '<span class="badge badge-work">Praca</span>'; } } else if (entry.type === 'duel') { if (entry.source.includes('bandytą') || entry.source.includes('bandit')) { typeBadge = '<span class="badge badge-duel-bandit">Bandyta</span>'; } else { typeBadge = '<span class="badge badge-duel-player">PvP</span>'; } } else if (entry.type === 'battle') { typeBadge = '<span class="badge badge-battle">Bitwa</span>'; } else { typeBadge = '<span class="badge badge-other">Inne</span>'; } const entryDiv = document.createElement('div'); entryDiv.className = 'entry'; entryDiv.innerHTML = '<span class="timestamp">' + entry.timestamp + '</span>' + '<span class="source">' + typeBadge + entry.source + '</span>' + '<span class="amount">+' + entry.amount.toLocaleString() + ' XP</span>'; listDiv.appendChild(entryDiv); }); } updatePagination(); } catch (error) { showError('Błąd podczas renderowania strony: ' + error.message); } } function updatePagination() { const paginationDiv = document.getElementById('pagination'); const totalPages = Math.max(1, Math.ceil(filteredData.length / itemsPerPage)); let paginationHTML = ''; if (totalPages > 1) { if (currentPage > 1) { paginationHTML += '<button onclick="changePage(1)">«</button>'; paginationHTML += '<button onclick="changePage(' + (currentPage - 1) + ')">‹</button>'; } for (let i = Math.max(1, currentPage - 2); i <= Math.min(totalPages, currentPage + 2); i++) { paginationHTML += '<button onclick="changePage(' + i + ')" ' + (i === currentPage ? 'class="active"' : '') + '>' + i + '</button>'; } if (currentPage < totalPages) { paginationHTML += '<button onclick="changePage(' + (currentPage + 1) + ')">›</button>'; paginationHTML += '<button onclick="changePage(' + totalPages + ')">»</button>'; } } paginationHTML += '<span class="info">Strona ' + currentPage + ' z ' + totalPages + ' (' + filteredData.length + ' wpisów)</span>'; paginationDiv.innerHTML = paginationHTML; } function changePage(newPage) { currentPage = newPage; renderPage(); window.scrollTo(0, 0); } // Inicjalizacja try { filteredData = [...originalData]; updateSummary(); applyFilters(); } catch (error) { showError('Błąd podczas inicjalizacji: ' + error.message); } </script> </body> </html> `); logWindow.document.close(); } // Funkcja resetująca doświadczenie do 0 function resetExperience() { if (confirm('Jesteś pewien, że chcesz zresetować całkowite doświadczenie?')) { totalExperience = 0; experienceLog = []; // Zapisz zresetowane wartości w localStorage localStorage.setItem('totalExperience', JSON.stringify(totalExperience)); localStorage.setItem('experienceLog', JSON.stringify(experienceLog)); // Zaktualizuj wyświetlaną wartość doświadczenia const totalExperienceElement = document.querySelector('#total-experience'); if (totalExperienceElement) { totalExperienceElement.textContent = totalExperience + ' XP'; } // Zaktualizuj statystyki z prac i pojedynków const workXPElement = document.querySelector('#work-xp'); const duelXPElement = document.querySelector('#duel-xp'); if (workXPElement) workXPElement.textContent = '0 XP'; if (duelXPElement) duelXPElement.textContent = '0 XP'; } } // Nasłuchiwanie na zmiany w doświadczeniu (np. praca, bitwa) const originalSetExperience = window.Character.setExperience; window.Character.setExperience = function(newExperience) { const currentExperience = this.experience; const gainedExperience = newExperience - currentExperience; if (gainedExperience > 0) { addExperience(gainedExperience, 'Aktywność w grze'); // Domyślna aktywność } originalSetExperience.call(this, newExperience); }; // Dodajemy nową funkcję do tworzenia panelu wyboru function showCategorySelectionPanel(action) { const existingPanel = document.querySelector('#category-selection-panel'); if (existingPanel) existingPanel.remove(); const panel = document.createElement('div'); panel.id = 'category-selection-panel'; panel.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(20, 20, 20, 0.95); padding: 20px; border-radius: 8px; border: 1px solid #444; z-index: 1001; color: white; min-width: 300px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); `; const title = action === 'collect' ? 'Zbieranie danych' : 'Aktualizacja danych'; panel.innerHTML = ` <div style="margin-bottom: 15px; text-align: center; font-size: 16px; color: #FFD700;"> ${title} </div> <div style="display: grid; gap: 10px;"> <label style=" display: flex; align-items: center; padding: 10px; background: #2C3E50; border-radius: 4px; cursor: pointer; transition: background 0.2s; "> <input type="checkbox" data-folder="all" class="category-checkbox" style="margin-right: 10px;"> Wszystkie raporty </label> <label style=" display: flex; align-items: center; padding: 10px; background: #27AE60; border-radius: 4px; cursor: pointer; transition: background 0.2s; "> <input type="checkbox" data-folder="job" class="category-checkbox" style="margin-right: 10px;"> PRACE </label> <label style=" display: flex; align-items: center; padding: 10px; background: #E74C3C; border-radius: 4px; cursor: pointer; transition: background 0.2s; "> <input type="checkbox" data-folder="duel" class="category-checkbox" style="margin-right: 10px;"> POJEDYNKI </label> <label style=" display: flex; align-items: center; padding: 10px; background: #9B59B6; border-radius: 4px; cursor: pointer; transition: background 0.2s; "> <input type="checkbox" data-folder="fortbattle" class="category-checkbox" style="margin-right: 10px;"> BITWY FORTOWE </label> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 10px;"> <button id="start-collection" style=" padding: 10px; background: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer; transition: background 0.2s; ">Start</button> <button id="cancel-selection" style=" padding: 10px; background: #95A5A6; color: white; border: none; border-radius: 4px; cursor: pointer; transition: background 0.2s; ">Anuluj</button> </div> </div> `; // Dodajemy efekt hover dla przycisków i labelów const buttons = panel.getElementsByTagName('button'); for (let button of buttons) { button.addEventListener('mouseover', function() { this.style.filter = 'brightness(120%)'; }); button.addEventListener('mouseout', function() { this.style.filter = 'brightness(100%)'; }); } const labels = panel.getElementsByTagName('label'); for (let label of labels) { label.addEventListener('mouseover', function() { this.style.filter = 'brightness(120%)'; }); label.addEventListener('mouseout', function() { this.style.filter = 'brightness(100%)'; }); } // Obsługa checkboxa "Wszystkie raporty" const allCheckbox = panel.querySelector('input[data-folder="all"]'); allCheckbox.addEventListener('change', function() { const checkboxes = panel.querySelectorAll('.category-checkbox:not([data-folder="all"])'); checkboxes.forEach(checkbox => { checkbox.checked = false; checkbox.disabled = this.checked; }); }); // Obsługa pozostałych checkboxów const otherCheckboxes = panel.querySelectorAll('.category-checkbox:not([data-folder="all"])'); otherCheckboxes.forEach(checkbox => { checkbox.addEventListener('change', function() { if (this.checked) { allCheckbox.checked = false; } }); }); // Obsługa przycisku Start panel.querySelector('#start-collection').addEventListener('click', async function() { const selectedFolders = Array.from(panel.querySelectorAll('.category-checkbox:checked')) .map(cb => cb.dataset.folder); if (selectedFolders.length === 0) { alert('Wybierz przynajmniej jedną kategorię!'); return; } panel.remove(); if (selectedFolders.includes('all')) { // Jeśli wybrano "wszystkie", ignoruj pozostałe if (action === 'collect') { collectExperienceHistory(null, 'all'); } else { updateExperienceHistory('all'); } } else { // Sekwencyjne przetwarzanie wybranych kategorii const processCategories = async () => { for (let i = 0; i < selectedFolders.length; i++) { const folder = selectedFolders[i]; console.log(`Rozpoczynam zbieranie danych dla ${getCategoryName(folder)}`); await new Promise((resolve) => { const processCategory = () => { if (action === 'collect') { collectExperienceHistory(null, folder, () => { console.log(`Zakończono przetwarzanie ${getCategoryName(folder)}`); setTimeout(resolve, 2000); }); } else { updateExperienceHistory(folder, () => { console.log(`Zakończono przetwarzanie ${getCategoryName(folder)}`); setTimeout(resolve, 2000); }); } }; // Jeśli to nie pierwsza kategoria, poczekaj chwilę przed rozpoczęciem if (i > 0) { setTimeout(processCategory, 2000); } else { processCategory(); } }); if (shouldCancel) { console.log('Anulowano zbieranie pozostałych danych'); showError('Anulowano zbieranie pozostałych danych.'); break; } } if (!shouldCancel) { showError('Zakończono przetwarzanie wszystkich wybranych kategorii!'); } }; // Rozpocznij przetwarzanie kategorii processCategories().catch(error => { console.error('Błąd podczas przetwarzania kategorii:', error); showError('Wystąpił błąd podczas przetwarzania kategorii'); }); } }); // Poprawiona obsługa przycisku Anuluj panel.querySelector('#cancel-selection').addEventListener('click', () => { panel.remove(); shouldCancel = true; // Upewnij się, że wszystkie trwające operacje zostaną anulowane }); document.body.appendChild(panel); } // Modyfikacja funkcji updateExperienceHistory function updateExperienceHistory(folder = 'all', callback = null) { if (isCollectingHistory) { console.log('Aktualizacja danych jest już w trakcie. Poczekaj na zakończenie.'); if (callback) callback(); return; } console.log(`Rozpoczynam aktualizację danych dla ${getCategoryName(folder)}`); isCollectingHistory = true; isPaused = false; shouldCancel = false; tempExpLog = []; let processedPages = 0; let failedAttempts = 0; const MAX_RETRIES = 3; const MIN_DELAY = 2000; // minimalne opóźnienie między zapytaniami (2 sekundy) const MAX_DELAY = 2200; // maksymalne opóźnienie między zapytaniami (2.2 sekundy) const RETRY_DELAY = 3000; // opóźnienie przy ponownej próbie po błędzie (3 sekundy) // Pokaż przyciski kontrolne i ukryj standardowe const controlButtons = document.querySelector('#collection-controls'); const standardButtons = document.querySelector('#standard-buttons'); if (controlButtons) { controlButtons.style.display = 'grid'; } if (standardButtons) { standardButtons.style.display = 'none'; } // Tworzymy zbiór istniejących raportów (unikalny po source+timestamp) const existingKeys = new Set(experienceLog.map(e => `${e.source}|${e.timestamp}`)); let foundDuplicate = false; function getRandomDelay() { return Math.floor(Math.random() * (MAX_DELAY - MIN_DELAY + 1)) + MIN_DELAY; } Ajax.remoteCall('reports', 'get_reports', { page: 1, folder: folder }, function(initialData) { if (!initialData || initialData.error) { console.error('Błąd pobierania danych:', initialData); isCollectingHistory = false; alert('Nie udało się pobrać informacji o raportach. Spróbuj ponownie.'); return; } const totalPages = initialData.count; function processPage(page) { if (!isCollectingHistory || shouldCancel || page > totalPages || foundDuplicate) { finishUpdate(shouldCancel); return; } if (isPaused) { setTimeout(() => processPage(page), 500); return; } Ajax.remoteCall('reports', 'get_reports', { page: page, folder: folder }, function(data) { if (!isCollectingHistory || shouldCancel || foundDuplicate) { finishUpdate(shouldCancel); return; } if (data && !data.error && data.reports && data.reports.length > 0) { failedAttempts = 0; for (const report of data.reports) { const expMatch = report.popupData.match(/experience.png[^>]*>(?:[^<]*<\/[^>]+>)*[^<]*<td>(\d+)<\/td>/) || report.popupData.match(/Doświadczenie<\/span>\s*<span[^>]*>(\d+)\s*punktów/); const exp = expMatch ? parseInt(expMatch[1]) : 0; if (exp > 0) { const key = `${report.title}|${report.date_received}`; if (existingKeys.has(key)) { foundDuplicate = true; break; } const reportType = report.title.includes('Raport dot. pracy') ? 'work' : report.title.includes('Pojedynek') ? 'duel' : (report.title.includes('Bitwa') || report.title.includes('Fort')) ? 'battle' : 'other'; tempExpLog.push({ amount: exp, source: report.title, timestamp: report.date_received, page: page, type: reportType }); } } processedPages++; updateCollectionStatus(processedPages, totalPages, tempExpLog.length); if (!foundDuplicate && !shouldCancel) { setTimeout(() => processPage(page + 1), getRandomDelay()); } else { finishUpdate(shouldCancel); } } else { failedAttempts++; if (failedAttempts < MAX_RETRIES && !shouldCancel) { setTimeout(() => processPage(page), RETRY_DELAY); } else { finishUpdate(shouldCancel); } } }); } processPage(1); }); function finishUpdate(wasCancelled = false) { console.log(`Kończę aktualizację danych dla ${getCategoryName(folder)}`); isCollectingHistory = false; isPaused = false; // Przywróć standardowe przyciski i ukryj kontrolne const controlButtons = document.querySelector('#collection-controls'); const standardButtons = document.querySelector('#standard-buttons'); if (controlButtons) { controlButtons.style.display = 'none'; } if (standardButtons) { standardButtons.style.display = 'grid'; } if (wasCancelled) { showError('Anulowano aktualizację danych.'); if (callback) callback(); return; } if (tempExpLog.length === 0) { showError(`Brak nowych wpisów w kategorii ${getCategoryName(folder)}.`); if (callback) callback(); return; } // Dodaj nowe wpisy na początek loga (bo są najnowsze) experienceLog = tempExpLog.concat(experienceLog); totalExperience = experienceLog.reduce((sum, entry) => sum + entry.amount, 0); localStorage.setItem('experienceLog', JSON.stringify(experienceLog)); localStorage.setItem('totalExperience', JSON.stringify(totalExperience)); updateDisplay(); updateXPStats(); showError(`Zaktualizowano ${getCategoryName(folder)}! Dodano ${tempExpLog.length} nowych wpisów.`); if (callback) { console.log(`Wywołuję callback dla kategorii: ${getCategoryName(folder)}`); callback(); } } } // Funkcja do sprawdzania ilości stron dla różnych typów raportów function checkReportPages() { const types = [ { name: 'Wszystkie raporty', params: { folder: 'all' } }, { name: 'Raporty z pracy', params: { folder: 'job' } }, { name: 'Raporty z pojedynków', params: { folder: 'duel' } }, { name: 'Raporty z bitew fortowych', params: { folder: 'fortbattle' } } ]; let results = {}; let completed = 0; console.log('%c=== Rozpoczynam sprawdzanie raportów ===', 'font-weight: bold; font-size: 14px; color: #3498db;'); types.forEach(type => { console.log(`%cSprawdzam: ${type.name}...`, 'color: #7f8c8d;'); Ajax.remoteCall('reports', 'get_reports', { page: 1, ...type.params }, function(response) { completed++; console.log(`%cOtrzymano odpowiedź dla: ${type.name}`, 'color: #7f8c8d;'); console.log('Surowa odpowiedź:', response); if (response && !response.error) { results[type.name] = { totalPages: response.pages || response.count || 0, reportsPerPage: response.reports ? response.reports.length : 0, totalReports: response.total || (response.reports ? response.reports.length * (response.pages || response.count || 0) : 0) }; } else { results[type.name] = { error: response ? response.error : 'Brak odpowiedzi' }; } if (completed === types.length) { console.log('%c=== Statystyki raportów ===', 'font-weight: bold; font-size: 14px; color: #3498db;'); Object.entries(results).forEach(([name, data]) => { console.log(`%c${name}:`, 'font-weight: bold; color: #2ecc71;'); if (data.error) { console.log(` Błąd: ${data.error}`); } else { console.log(` Liczba stron: ${data.totalPages}`); console.log(` Raportów na stronę: ${data.reportsPerPage}`); console.log(` Łączna liczba raportów: ${data.totalReports}`); if (data.reportsPerPage > 0) { console.log(` Szacowana łączna liczba raportów: ${data.totalPages * data.reportsPerPage}`); } } console.log('------------------------'); }); } }); }); return "Sprawdzanie ilości stron... Zobacz wyniki w konsoli."; } // Dodaj do window aby było dostępne w konsoli window.checkReportPages = checkReportPages; // Dodanie okna śledzenia doświadczenia setInterval(() => { addExperienceWindow(); createVisibilityToggle(); }, 1000); // Funkcja do przełączania widoczności function toggleTrackerVisibility() { const tracker = document.querySelector('#experience-tracker'); const toggleCheckbox = document.querySelector('#toggle-tracker-visibility'); if (tracker && toggleCheckbox) { isTrackerVisible = toggleCheckbox.checked; tracker.style.display = isTrackerVisible ? 'block' : 'none'; localStorage.setItem('experienceTrackerVisible', JSON.stringify(isTrackerVisible)); } } // Funkcja do tworzenia checkboxa widoczności function createVisibilityToggle() { const existingToggle = document.querySelector('#tracker-visibility-toggle'); if (existingToggle) return; const toggleContainer = document.createElement('div'); toggleContainer.id = 'tracker-visibility-toggle'; // Pozycjonowanie względem ui_topbar const uiTopbar = document.querySelector('#ui_topbar'); const topbarRect = uiTopbar ? uiTopbar.getBoundingClientRect() : null; toggleContainer.style.cssText = ` position: fixed; top: ${topbarRect ? topbarRect.top + 'px' : '10px'}; right: ${topbarRect ? (window.innerWidth - topbarRect.left) + 'px' : '310px'}; background: rgba(20, 20, 20, 0.95); padding: 5px 10px; border-radius: 4px; border: 1px solid #444; z-index: 1000; color: white; font-family: Arial, sans-serif; font-size: 12px; display: flex; align-items: center; gap: 5px; cursor: pointer; `; toggleContainer.innerHTML = ` <input type="checkbox" id="toggle-tracker-visibility" ${isTrackerVisible ? 'checked' : ''}> `; document.body.appendChild(toggleContainer); // Aktualizacja pozycji przy zmianie rozmiaru okna window.addEventListener('resize', () => { const uiTopbar = document.querySelector('#ui_topbar'); const topbarRect = uiTopbar ? uiTopbar.getBoundingClientRect() : null; if (topbarRect && toggleContainer) { toggleContainer.style.top = topbarRect.top + 'px'; toggleContainer.style.right = (window.innerWidth - topbarRect.left) + 'px'; } }); const checkbox = toggleContainer.querySelector('#toggle-tracker-visibility'); checkbox.addEventListener('change', toggleTrackerVisibility); } })();