// ==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();
}
})();