您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Gym training helper with target percentages, current distribution display, and optional raw differences
// ==UserScript== // @name Torn Gym Ratios (PDA Compatible) // @namespace http://tampermonkey.net/ // @version 3.1 // @description Gym training helper with target percentages, current distribution display, and optional raw differences // @author Mistborn [3037268] // @match https://www.torn.com/gym.php* // @icon https://www.google.com/s2/favicons?sz=64&domain=torn.com // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @run-at document-end // @license MIT // @supportURL https://github.com/MistbornTC/torn-gym-ratios/issues // ==/UserScript== (function() { 'use strict'; console.log('=== TORN GYM RATIOS SCRIPT LOADED ==='); // Enhanced PDA detection function isTornPDA() { const userAgentCheck = navigator.userAgent.includes('com.manuito.tornpda'); const flutterCheck = !!window.flutter_inappwebview; const platformCheck = !!window.__PDA_platformReadyPromise; const httpCheck = !!window.PDA_httpGet; const result = !!(userAgentCheck || flutterCheck || platformCheck || httpCheck); console.log('PDA Detection:', result, { userAgent: userAgentCheck, flutter: flutterCheck, platform: platformCheck, httpGet: httpCheck }); return result; } // Universal storage functions function safeGM_getValue(key, defaultValue) { try { if (typeof GM_getValue !== 'undefined') { return GM_getValue(key, defaultValue); } } catch (e) { console.log('GM_getValue failed, using localStorage fallback'); } try { const stored = localStorage.getItem('gym_' + key); return stored ? JSON.parse(stored) : defaultValue; } catch (e) { console.log('localStorage failed, using default:', defaultValue); return defaultValue; } } function safeGM_setValue(key, value) { try { if (typeof GM_setValue !== 'undefined') { GM_setValue(key, value); return; } } catch (e) { console.log('GM_setValue failed, using localStorage fallback'); } try { localStorage.setItem('gym_' + key, JSON.stringify(value)); } catch (e) { console.log('Storage failed'); } } // Format numbers with appropriate abbreviations function formatNumber(num) { if (num < 100000) { return num.toLocaleString(); } else if (num < 1000000) { return Math.round(num / 1000) + 'k'; } else if (num < 1000000000) { const millions = num / 1000000; return millions % 1 < 0.05 ? Math.round(millions) + 'M' : millions.toFixed(1) + 'M'; } else { const billions = num / 1000000000; if (billions % 1 < 0.005) return Math.round(billions) + 'B'; return parseFloat(billions.toFixed(2)) + 'B'; // Auto-trims trailing zeros } } // Calculate stat difference text and color function getStatDifferenceText(statName, currentValue, targetValue, color, percentageDiff) { const difference = Math.abs(currentValue - targetValue); const formattedDiff = formatNumber(Math.round(difference)); const isMobile = window.innerWidth <= 768; if (Math.abs(percentageDiff) <= 1) { // Within 1% tolerance - use the same logic as color determination const sign = currentValue >= targetValue ? '+' : '-'; return { text: `${statName} on target (${sign}${formattedDiff})`, color: color }; } else if (currentValue > targetValue) { // Over target const suffix = isMobile ? ' over' : ' over target'; return { text: `${statName} ${formattedDiff}${suffix}`, color: color }; } else { // Under target const suffix = isMobile ? ' under' : ' under target'; return { text: `${statName} ${formattedDiff}${suffix}`, color: color }; } } // Wait for element function waitForElement(selector, callback, timeout = 30000) { const startTime = Date.now(); function check() { const element = document.querySelector(selector); if (element) { console.log('Found element:', selector); callback(element); return; } if (Date.now() - startTime > timeout) { console.error('Timeout waiting for element:', selector); return; } setTimeout(check, 100); } check(); } // Extract current stat values function getCurrentStats() { const stats = { strength: 0, defense: 0, speed: 0, dexterity: 0 }; const strengthContainer = document.querySelector('li[class*="strength___"]'); const defenseContainer = document.querySelector('li[class*="defense___"]'); const speedContainer = document.querySelector('li[class*="speed___"]'); const dexterityContainer = document.querySelector('li[class*="dexterity___"]'); function extractValue(container) { if (!container) return 0; const valueEl = container.querySelector('[class*="propertyValue___"]') || container.querySelector('.propertyValue') || container.querySelector('[data-value]'); if (valueEl) { const text = valueEl.textContent || valueEl.getAttribute('data-value') || '0'; return parseInt(text.replace(/,/g, '')) || 0; } return 0; } stats.strength = extractValue(strengthContainer); stats.defense = extractValue(defenseContainer); stats.speed = extractValue(speedContainer); stats.dexterity = extractValue(dexterityContainer); return stats; } // Calculate percentages function calculateCurrentDistribution(stats) { const total = stats.strength + stats.defense + stats.speed + stats.dexterity; if (total === 0) return { strength: 0, defense: 0, speed: 0, dexterity: 0 }; return { strength: (stats.strength / total * 100).toFixed(1), defense: (stats.defense / total * 100).toFixed(1), speed: (stats.speed / total * 100).toFixed(1), dexterity: (stats.dexterity / total * 100).toFixed(1) }; } // Load/save targets and settings function loadTargets() { return { strength: safeGM_getValue('gym_target_strength', 25), defense: safeGM_getValue('gym_target_defense', 25), speed: safeGM_getValue('gym_target_speed', 25), dexterity: safeGM_getValue('gym_target_dexterity', 25) }; } function saveTargets(targets) { safeGM_setValue('gym_target_strength', targets.strength); safeGM_setValue('gym_target_defense', targets.defense); safeGM_setValue('gym_target_speed', targets.speed); safeGM_setValue('gym_target_dexterity', targets.dexterity); } function loadShowStatDifferences() { return safeGM_getValue('gym_show_stat_differences', false); } function saveShowStatDifferences(show) { safeGM_setValue('gym_show_stat_differences', show); } // Theme detection function getTheme() { const body = document.body; const isDarkMode = body.classList.contains('dark-mode') || body.classList.contains('dark') || body.style.background.includes('#191919') || getComputedStyle(body).backgroundColor === 'rgb(25, 25, 25)'; return isDarkMode ? 'dark' : 'light'; } function getThemeColors() { const theme = getTheme(); if (theme === 'dark') { return { panelBg: '#2a2a2a', panelBorder: '#444', configBg: '#333', configBorder: '#555', statBoxBg: '#3a3a3a', statBoxBorder: '#444', statBoxShadow: '0 1px 3px rgba(0,0,0,0.1)', inputBg: '#444', inputBorder: '#555', textPrimary: '#fff', textSecondary: '#ccc', textMuted: '#999', success: '#5cb85c', warning: '#f0ad4e', danger: '#d9534f', primary: '#4a90e2', neutral: '#666' }; } else { return { panelBg: '#f8f9fa', panelBorder: 'rgba(102, 102, 102, 0.3)', configBg: '#ffffff', configBorder: '#ced4da', statBoxBg: '#ffffff', statBoxBorder: 'rgba(102, 102, 102, 0.3)', statBoxShadow: 'rgba(50, 50, 50, 0.2) 0px 0px 2px 0px', inputBg: '#ffffff', inputBorder: '#ced4da', textPrimary: '#212529', textSecondary: '#6c757d', textMuted: '#adb5bd', success: 'rgb(105, 168, 41)', warning: '#ffc107', danger: '#dc3545', primary: '#007bff', neutral: '#6c757d' }; } } // Collapse state management function isCollapsed() { if (isTornPDA()) { const statsDisplay = document.getElementById('gym-stats-display'); return statsDisplay ? statsDisplay.style.display === 'none' : false; } const isMobile = window.innerWidth <= 768; const key = isMobile ? 'gym_helper_collapsed_mobile' : 'gym_helper_collapsed_desktop'; return safeGM_getValue(key, false); } function setCollapsed(collapsed) { if (isTornPDA()) { return; // No storage in PDA } const isMobile = window.innerWidth <= 768; const key = isMobile ? 'gym_helper_collapsed_mobile' : 'gym_helper_collapsed_desktop'; safeGM_setValue(key, collapsed); } // Track current theme to detect changes let currentTheme = getTheme(); // Dynamic theme update function function updateMainContainerTheme() { const newTheme = getTheme(); if (newTheme !== currentTheme) { currentTheme = newTheme; const colors = getThemeColors(); const mainPanel = document.getElementById('gym-helper-display'); if (mainPanel) { mainPanel.style.background = colors.panelBg; mainPanel.style.border = '1px solid ' + colors.panelBorder; mainPanel.style.color = colors.textPrimary; mainPanel.style.boxShadow = colors.statBoxShadow; // Update title color const title = mainPanel.querySelector('#gym-header-clickable'); if (title) title.style.color = colors.textPrimary; // Update button colors const helpBtn = document.getElementById('gym-help-btn'); const collapseBtn = document.getElementById('gym-collapse-btn'); const configBtn = document.getElementById('gym-config-btn'); if (helpBtn) helpBtn.style.background = colors.neutral; if (collapseBtn) collapseBtn.style.background = colors.neutral; if (configBtn) configBtn.style.background = colors.primary; // Update help tooltip const tooltip = document.getElementById('gym-help-tooltip'); if (tooltip) { tooltip.style.background = colors.statBoxBg; tooltip.style.border = '1px solid ' + colors.statBoxBorder; tooltip.style.boxShadow = colors.statBoxShadow; const tooltipElements = tooltip.querySelectorAll('div'); tooltipElements.forEach(el => { el.style.color = colors.textPrimary; }); // Update the Yellow/Orange text based on theme const middleDiv = tooltip.children[2]; if (middleDiv) { const newColorName = newTheme === 'light' ? 'Yellow' : 'Orange'; middleDiv.innerHTML = '<span style="color: ' + colors.warning + '; font-weight: bold; font-size: 16px;">|</span> <span style="font-weight: bold;">' + newColorName + ':</span> Above target (focus on other stats)'; } } // Update config panel const configPanel = document.getElementById('gym-config-panel'); if (configPanel) { configPanel.style.background = colors.configBg; configPanel.style.border = '1px solid ' + colors.configBorder; const configTitle = configPanel.querySelector('h4'); if (configTitle) configTitle.style.color = colors.textPrimary; const configLabels = configPanel.querySelectorAll('label'); configLabels.forEach(label => { label.style.color = colors.textSecondary; }); const configInputs = configPanel.querySelectorAll('input'); configInputs.forEach(input => { input.style.background = colors.inputBg; input.style.border = '1px solid ' + colors.inputBorder; input.style.color = colors.textPrimary; }); const saveBtn = configPanel.querySelector('#save-targets'); const cancelBtn = configPanel.querySelector('#cancel-config'); if (saveBtn) saveBtn.style.background = colors.success; if (cancelBtn) cancelBtn.style.background = colors.danger; } } } } // Create the main display panel using innerHTML (PDA-compatible) function createDisplayPanel() { const colors = getThemeColors(); const panel = document.createElement('div'); panel.id = 'gym-helper-display'; panel.style.cssText = ` background: ${colors.panelBg}; border: 1px solid ${colors.panelBorder}; border-radius: 5px; padding: 15px; margin: 10px 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; color: ${colors.textPrimary}; position: relative; box-shadow: ${colors.statBoxShadow}; `; panel.innerHTML = ` <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;"> <h3 id="gym-header-clickable" style=" margin: 0; color: ${colors.textPrimary}; font-size: 16px; user-select: none; cursor: pointer; ">Gym Ratios</h3> <div style="margin-left: auto; margin-right: -21px;"> <button id="gym-help-btn" style=" background: ${colors.neutral}; color: white; border: none; padding: 5px 8px; border-radius: 3px; cursor: pointer; font-size: 12px; margin-right: 5px; ${(!isTornPDA() && window.innerWidth > 768) ? '-webkit-transform: translateZ(0); transform: translateZ(0); -webkit-backface-visibility: hidden; backface-visibility: hidden;' : 'outline: none; -webkit-appearance: none; appearance: none; border: none; position: relative; overflow: hidden;'} ">?</button> <button id="gym-collapse-btn" style=" background: ${colors.neutral}; color: white; border: none; padding: 5px 8px; border-radius: 3px; cursor: pointer; font-size: 12px; margin-right: 5px; ${(!isTornPDA() && window.innerWidth > 768) ? '-webkit-transform: translateZ(0); transform: translateZ(0); -webkit-backface-visibility: hidden; backface-visibility: hidden;' : 'outline: none; -webkit-appearance: none; appearance: none; border: none; position: relative; overflow: hidden;'} ">−</button> <button id="gym-config-btn" style=" background: ${colors.primary}; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; font-size: 12px; ${(!isTornPDA() && window.innerWidth > 768) ? '-webkit-transform: translateZ(0); transform: translateZ(0); -webkit-backface-visibility: hidden; backface-visibility: hidden;' : 'outline: none; -webkit-appearance: none; appearance: none; border: none; position: relative; overflow: hidden;'} ">Config</button> </div> </div> <div id="gym-help-tooltip" style=" position: absolute; top: 100%; left: 0; right: 0; background: ${colors.statBoxBg}; border: 1px solid ${colors.statBoxBorder}; border-radius: 5px; padding: 12px; margin-top: 5px; z-index: 1001; display: none; box-shadow: ${colors.statBoxShadow}; font-size: 13px; line-height: 1.4; "> <div style="margin-bottom: 8px; font-weight: bold; color: ${colors.textPrimary};">Color Guide:</div> <div style="margin-bottom: 6px; color: ${colors.textPrimary};"> <span style="color: ${colors.success}; font-weight: bold; font-size: 16px;">|</span> <span style="font-weight: bold;">Green:</span> On target (within ±1% of target) </div> <div style="margin-bottom: 6px; color: ${colors.textPrimary};"> <span style="color: ${colors.warning}; font-weight: bold; font-size: 16px;">|</span> <span style="font-weight: bold;">${getTheme() === 'light' ? 'Yellow' : 'Orange'}:</span> Above target (focus on other stats) </div> <div style="color: ${colors.textPrimary};"> <span style="color: ${colors.danger}; font-weight: bold; font-size: 16px;">|</span> <span style="font-weight: bold;">Red:</span> Below target (needs more training) </div> </div> <div id="gym-stats-display" style=" display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; font-size: 13px; "></div> <style> @media (max-width: 768px) { #gym-stats-display { grid-template-columns: repeat(2, 1fr) !important; gap: 8px !important; font-size: 12px !important; } #gym-stats-display > div { padding: 8px !important; } #gym-helper-display { padding: 10px !important; margin: 5px 0 !important; } } @media (max-width: 480px) { #gym-stats-display { grid-template-columns: repeat(2, 1fr) !important; gap: 6px !important; font-size: 11px !important; } #gym-helper-display { margin-left: -5px !important; position: relative !important; z-index: 100 !important; } #gym-config-panel, #gym-help-tooltip { left: 0 !important; right: 0 !important; margin-left: 0 !important; margin-right: 0 !important; z-index: 1010 !important; } } </style> `; return panel; } // Create config panel using innerHTML (PDA-compatible) function createConfigPanel() { const colors = getThemeColors(); const targets = loadTargets(); const showStatDifferences = loadShowStatDifferences(); const configPanel = document.createElement('div'); configPanel.id = 'gym-config-panel'; configPanel.style.cssText = ` position: absolute; top: 100%; left: 0; right: 0; background: ${colors.configBg}; border: 1px solid ${colors.configBorder}; border-radius: 5px; padding: 15px; margin-top: 5px; z-index: 1000; display: none; box-shadow: 0 4px 8px rgba(0,0,0,0.15); `; configPanel.innerHTML = ` <div style="display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 15px;"> <h4 style="margin: 0; padding: 0; color: ${colors.textPrimary}; font-size: 16px;">Target Percentages</h4> <span id="total-percentage" style="margin: 0; padding: 0; color: ${colors.textSecondary}; font-weight: bold; font-size: 12px;">Total: 100%</span> </div> <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; margin-bottom: 15px;"> <div> <label style="display: block; margin-bottom: 5px; color: ${colors.textSecondary};">Strength (%)</label> <input type="number" id="target-strength" value="${targets.strength}" min="0" max="100" step="0.1" style=" width: 100%; padding: 5px; border: 1px solid ${colors.inputBorder}; border-radius: 3px; background: ${colors.inputBg}; color: ${colors.textPrimary}; box-sizing: border-box; "> </div> <div> <label style="display: block; margin-bottom: 5px; color: ${colors.textSecondary};">Defense (%)</label> <input type="number" id="target-defense" value="${targets.defense}" min="0" max="100" step="0.1" style=" width: 100%; padding: 5px; border: 1px solid ${colors.inputBorder}; border-radius: 3px; background: ${colors.inputBg}; color: ${colors.textPrimary}; box-sizing: border-box; "> </div> <div> <label style="display: block; margin-bottom: 5px; color: ${colors.textSecondary};">Speed (%)</label> <input type="number" id="target-speed" value="${targets.speed}" min="0" max="100" step="0.1" style=" width: 100%; padding: 5px; border: 1px solid ${colors.inputBorder}; border-radius: 3px; background: ${colors.inputBg}; color: ${colors.textPrimary}; box-sizing: border-box; "> </div> <div> <label style="display: block; margin-bottom: 5px; color: ${colors.textSecondary};">Dexterity (%)</label> <input type="number" id="target-dexterity" value="${targets.dexterity}" min="0" max="100" step="0.1" style=" width: 100%; padding: 5px; border: 1px solid ${colors.inputBorder}; border-radius: 3px; background: ${colors.inputBg}; color: ${colors.textPrimary}; box-sizing: border-box; "> </div> </div> <div style="margin-bottom: 15px;"> <label style="display: flex; align-items: center; color: ${colors.textSecondary}; cursor: pointer;"> <input type="checkbox" id="show-stat-differences" ${showStatDifferences ? 'checked' : ''} style=" margin-right: 8px; transform: scale(1.1); "> Include raw stat difference? </label> </div> <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px;"> <button id="save-targets" style=" background: ${colors.success}; color: white; border: none; padding: 8px 12px; border-radius: 3px; cursor: pointer; font-size: 12px; width: 100%; box-sizing: border-box; ">Save</button> <button id="cancel-config" style=" background: ${colors.danger}; color: white; border: none; padding: 8px 12px; border-radius: 3px; cursor: pointer; font-size: 12px; width: 100%; box-sizing: border-box; ">Cancel</button> </div> `; return configPanel; } // Toggle collapse state function toggleCollapsed() { const statsDisplay = document.getElementById('gym-stats-display'); const collapseBtn = document.getElementById('gym-collapse-btn'); const configPanel = document.getElementById('gym-config-panel'); const mainPanel = document.getElementById('gym-helper-display'); const header = mainPanel ? mainPanel.querySelector('div:first-child') : null; if (!statsDisplay || !collapseBtn) return; const collapsed = isCollapsed(); const newState = !collapsed; setCollapsed(newState); if (newState) { // Collapse statsDisplay.style.display = 'none'; collapseBtn.innerHTML = '+'; if (configPanel) configPanel.style.display = 'none'; // Adjust spacing when collapsed for tighter layout if (header) header.style.marginBottom = '2px'; if (mainPanel) { if (isTornPDA()) { mainPanel.style.padding = '10px 15px 8px 15px'; mainPanel.style.marginBottom = '5px'; } else { mainPanel.style.padding = '10px 15px 8px 15px'; } } } else { // Expand statsDisplay.style.display = 'grid'; collapseBtn.innerHTML = '−'; // Restore normal spacing when expanded if (header) header.style.marginBottom = '10px'; if (mainPanel) { if (isTornPDA()) { mainPanel.style.padding = '15px'; mainPanel.style.marginBottom = '10px'; } else { mainPanel.style.padding = '15px'; } } updateStats(); } } // Apply saved collapse state function applySavedCollapseState() { if (isTornPDA()) { return; // Always start expanded in PDA } if (isCollapsed()) { setTimeout(() => { const statsDisplay = document.getElementById('gym-stats-display'); const collapseBtn = document.getElementById('gym-collapse-btn'); if (statsDisplay && collapseBtn) { statsDisplay.style.display = 'none'; collapseBtn.innerHTML = '+'; const mainPanel = document.getElementById('gym-helper-display'); if (mainPanel) mainPanel.style.paddingBottom = '5px'; } }, 100); } } // Update stats display function updateStats() { updateMainContainerTheme(); // Check for theme changes const stats = getCurrentStats(); const dist = calculateCurrentDistribution(stats); const targets = loadTargets(); const showStatDifferences = loadShowStatDifferences(); const colors = getThemeColors(); const statsDiv = document.getElementById('gym-stats-display'); if (!statsDiv) return; if (isCollapsed()) return; // Don't update if collapsed const total = stats.strength + stats.defense + stats.speed + stats.dexterity; if (total === 0) { statsDiv.innerHTML = '<div style="grid-column: 1/-1; text-align: center; color: ' + colors.textSecondary + ';">No stats found. Make sure you\'re on the gym page.</div>'; return; } const statNames = ['strength', 'defense', 'speed', 'dexterity']; const statLabels = ['Strength', 'Defense', 'Speed', 'Dexterity']; statsDiv.innerHTML = statNames.map((stat, index) => { const current = parseFloat(dist[stat]); const target = targets[stat]; const diff = current - target; const color = Math.abs(diff) <= 1 ? colors.success : (diff > 0 ? colors.warning : colors.danger); let additionalContent = ''; if (showStatDifferences) { // Calculate target value in absolute numbers const targetValue = (target / 100) * total; const currentValue = stats[stat]; // Get difference text and color const diffInfo = getStatDifferenceText(statLabels[index], currentValue, targetValue, color, diff); additionalContent = ` <div style="font-size: 10px; color: ${colors.textMuted}; margin-bottom: 10px;"> Target: ${target}% </div> <div style="font-size: 10px; color: ${diffInfo.color}; font-weight: bold;"> ${diffInfo.text} </div> `; } else { additionalContent = ` <div style="font-size: 10px; color: ${colors.textMuted};"> Target: ${target}% </div> `; } return ` <div style=" background: ${colors.statBoxBg}; padding: 10px; border-radius: 3px; text-align: center; border-left: 3px solid ${color}; border-top: 1px solid ${colors.statBoxBorder}; border-right: 1px solid ${colors.statBoxBorder}; border-bottom: 1px solid ${colors.statBoxBorder}; box-shadow: ${colors.statBoxShadow}; "> <div style="font-weight: bold; margin-bottom: 5px; color: ${colors.textPrimary};">${statLabels[index]}</div> <div style="color: ${color}; font-weight: bold; margin-bottom: 4px;"> ${current}% (${diff > 0 ? '+' : ''}${diff.toFixed(1)}) </div> ${additionalContent} </div> `; }).join(''); console.log('Stats updated' + (showStatDifferences ? ' with differences' : '')); } // Update total percentage in config function updateTotalPercentage() { const strength = parseFloat(document.getElementById('target-strength').value) || 0; const defense = parseFloat(document.getElementById('target-defense').value) || 0; const speed = parseFloat(document.getElementById('target-speed').value) || 0; const dexterity = parseFloat(document.getElementById('target-dexterity').value) || 0; const total = strength + defense + speed + dexterity; const totalSpan = document.getElementById('total-percentage'); const colors = getThemeColors(); if (totalSpan) { totalSpan.textContent = 'Total: ' + total.toFixed(1) + '%'; totalSpan.style.color = Math.abs(total - 100) <= 0.1 ? colors.success : colors.danger; } const saveBtn = document.getElementById('save-targets'); if (saveBtn) { saveBtn.disabled = Math.abs(total - 100) > 0.1; saveBtn.style.opacity = saveBtn.disabled ? '0.5' : '1'; } } // SPEED OPTIMIZED INITIALIZATION function initOptimized() { console.log('=== INITIALIZING PDA-FIXED SPEED OPTIMIZED SCRIPT ==='); console.log('Platform:', isTornPDA() ? 'PDA' : 'Browser'); console.log('URL:', window.location.href); if (isTornPDA()) { console.log('Using PDA-compatible (slower) initialization method'); waitForElement('.page-head-delimiter', function(delimiter) { console.log('Found page delimiter, creating panel'); const displayPanel = createDisplayPanel(); const configPanel = createConfigPanel(); displayPanel.appendChild(configPanel); delimiter.parentNode.insertBefore(displayPanel, delimiter.nextSibling); console.log('Panel inserted, waiting for stats to load'); setTimeout(function() { updateStats(); applySavedCollapseState(); setInterval(updateStats, 5000); }, 2000); setupEventListeners(); }, 15000); } else { console.log('Using browser-optimized (faster) initialization method'); waitForElement('li[class*="strength___"]', function(strengthEl) { console.log('Found strength element, checking for delimiter'); const delimiter = document.querySelector('.page-head-delimiter'); if (!delimiter) { console.log('Delimiter not found, retrying in 100ms'); setTimeout(initOptimized, 100); return; } console.log('Both stats and delimiter found, creating panel'); const displayPanel = createDisplayPanel(); const configPanel = createConfigPanel(); displayPanel.appendChild(configPanel); delimiter.parentNode.insertBefore(displayPanel, delimiter.nextSibling); console.log('Panel inserted, updating stats'); updateStats(); applySavedCollapseState(); setInterval(updateStats, 5000); setupEventListeners(); }, 5000); } } // Setup event listeners function setupEventListeners() { // Help button document.getElementById('gym-help-btn').addEventListener('click', () => { const tooltip = document.getElementById('gym-help-tooltip'); tooltip.style.display = tooltip.style.display === 'none' ? 'block' : 'none'; const configPanel = document.getElementById('gym-config-panel'); if (tooltip.style.display === 'block') configPanel.style.display = 'none'; }); // Header click and collapse button document.getElementById('gym-header-clickable').addEventListener('click', toggleCollapsed); document.getElementById('gym-collapse-btn').addEventListener('click', toggleCollapsed); // Config button document.getElementById('gym-config-btn').addEventListener('click', () => { if (isCollapsed()) { toggleCollapsed(); setTimeout(() => { const panel = document.getElementById('gym-config-panel'); if (panel) panel.style.display = 'block'; }, 100); } else { const panel = document.getElementById('gym-config-panel'); if (panel) { panel.style.display = panel.style.display === 'none' ? 'block' : 'none'; const tooltip = document.getElementById('gym-help-tooltip'); if (panel.style.display === 'block') tooltip.style.display = 'none'; } } }); // Config panel buttons document.getElementById('cancel-config').addEventListener('click', () => { document.getElementById('gym-config-panel').style.display = 'none'; }); document.getElementById('save-targets').addEventListener('click', () => { const targets = { strength: parseFloat(document.getElementById('target-strength').value), defense: parseFloat(document.getElementById('target-defense').value), speed: parseFloat(document.getElementById('target-speed').value), dexterity: parseFloat(document.getElementById('target-dexterity').value) }; const showStatDifferences = document.getElementById('show-stat-differences').checked; saveTargets(targets); saveShowStatDifferences(showStatDifferences); updateStats(); document.getElementById('gym-config-panel').style.display = 'none'; }); // Add input listeners for real-time validation ['target-strength', 'target-defense', 'target-speed', 'target-dexterity'].forEach(id => { document.getElementById(id).addEventListener('input', updateTotalPercentage); }); // Initial total percentage update updateTotalPercentage(); // Close panels when clicking outside document.addEventListener('click', (e) => { const helpBtn = document.getElementById('gym-help-btn'); const tooltip = document.getElementById('gym-help-tooltip'); const configBtn = document.getElementById('gym-config-btn'); const configPanel = document.getElementById('gym-config-panel'); if (!helpBtn.contains(e.target) && !tooltip.contains(e.target)) { tooltip.style.display = 'none'; } if (!configBtn.contains(e.target) && !configPanel.contains(e.target)) { configPanel.style.display = 'none'; } }); } // Start when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initOptimized); } else { initOptimized(); } })();