// Helper functions for the game // Helper function to increase saturation of RGB color function saturateColor(r, g, b, saturationBoost) { // Normalize RGB to 0-1 r /= 255; g /= 255; b /= 255; // Convert to HSL var max = Math.max(r, g, b); var min = Math.min(r, g, b); var h, s, l = (max + min) / 2; if (max === min) { h = s = 0; // achromatic } else { var d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min); switch (max) { case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break; case g: h = ((b - r) / d + 2) / 6; break; case b: h = ((r - g) / d + 4) / 6; break; } } // Boost saturation s = Math.min(1, s * saturationBoost); // Convert back to RGB function hue2rgb(p, q, t) { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1/6) return p + (q - p) * 6 * t; if (t < 1/2) return q; if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; return p; } var q = l < 0.5 ? l * (1 + s) : l + s - l * s; var p = 2 * l - q; r = Math.round(hue2rgb(p, q, h + 1/3) * 255); g = Math.round(hue2rgb(p, q, h) * 255); b = Math.round(hue2rgb(p, q, h - 1/3) * 255); return { r: r, g: g, b: b }; } function calculateConsumptionRates(state, CONFIG, consumedMassKg) { const now = Date.now(); // Only update rates once per second if (!state._lastRateUpdate) state._lastRateUpdate = 0; if (now - state._lastRateUpdate < 1000) return; // skip if less than 1s state._lastRateUpdate = now; function updateWindow(massKey, timeKey, prevRateKey) { const start = state[timeKey] || now; // Add new mass state[massKey] = (state[massKey] || 0) + consumedMassKg; const elapsed = now - start; const rate = elapsed > 0 ? state[massKey] / (elapsed / 1000) : 0; // Compare to previous rate to get trend if (state[prevRateKey] === undefined) state[prevRateKey] = rate; state[prevRateKey + 'Trend'] = rate > state[prevRateKey] ? 'up' : rate < state[prevRateKey] ? 'down' : 'same'; // Save current rate for next tick state[prevRateKey] = rate; return rate; } state.rateShort = updateWindow("sM", "sT", "prevRateShort"); state.rateMedium = updateWindow("mM", "mT", "prevRateMedium"); state.rateLong = updateWindow("lM", "lT", "prevRateLong"); } function formatOfflineTime(ms) { var seconds = Math.floor(ms / 1000); var minutes = Math.floor(seconds / 60); var hours = Math.floor(minutes / 60); var days = Math.floor(hours / 24); if (days > 0) { return days + ' day' + (days > 1 ? 's' : '') + ', ' + (hours % 24) + ' hour' + ((hours % 24) !== 1 ? 's' : ''); } else if (hours > 0) { return hours + ' hour' + (hours > 1 ? 's' : '') + ', ' + (minutes % 60) + ' min'; } else if (minutes > 0) { return minutes + ' minute' + (minutes > 1 ? 's' : ''); } else { return seconds + ' second' + (seconds > 1 ? 's' : ''); } } function showOfflineNotification(timeAway, massGained, rateName) { var notification = document.createElement('div'); notification.style.position = 'fixed'; notification.style.top = '50%'; notification.style.left = '50%'; notification.style.transform = 'translate(-50%, -50%)'; notification.style.background = 'rgba(20, 20, 30, 0.95)'; notification.style.border = '2px solid rgba(161, 161, 161, 0.5)'; notification.style.padding = '20px 30px'; notification.style.borderRadius = '8px'; notification.style.color = 'rgba(255, 255, 255, 0.9)'; notification.style.fontSize = '12px'; notification.style.zIndex = '10000'; notification.style.textAlign = 'center'; notification.style.lineHeight = '1.6'; notification.innerHTML = '