/** * BitcoinMinuteRefresh.js - Simplified Bitcoin-themed floating uptime monitor * * This module creates a Bitcoin-themed terminal that shows server uptime. * Now includes DeepSea theme support. */ const BitcoinMinuteRefresh = (function () { // Constants const STORAGE_KEY = 'bitcoin_last_refresh_time'; // For cross-page sync const BITCOIN_COLOR = '#f7931a'; const DEEPSEA_COLOR = '#0088cc'; // Private variables let terminalElement = null; let uptimeElement = null; let serverTimeOffset = 0; let serverStartTime = null; let uptimeInterval = null; let isInitialized = false; let refreshCallback = null; let currentThemeColor = BITCOIN_COLOR; // Default Bitcoin color /** * Apply the current theme color */ function applyThemeColor() { // Check if theme toggle is set to DeepSea const isDeepSeaTheme = localStorage.getItem('useDeepSeaTheme') === 'true'; currentThemeColor = isDeepSeaTheme ? DEEPSEA_COLOR : BITCOIN_COLOR; // Don't try to update DOM elements if they don't exist yet if (!terminalElement) return; // Update terminal colors if (isDeepSeaTheme) { terminalElement.style.borderColor = DEEPSEA_COLOR; terminalElement.style.color = DEEPSEA_COLOR; terminalElement.style.boxShadow = `0 0 5px rgba(0, 136, 204, 0.3)`; // Update header border const headerElement = terminalElement.querySelector('.terminal-header'); if (headerElement) { headerElement.style.borderColor = DEEPSEA_COLOR; } // Update terminal title const titleElement = terminalElement.querySelector('.terminal-title'); if (titleElement) { titleElement.style.color = DEEPSEA_COLOR; titleElement.style.textShadow = '0 0 5px rgba(0, 136, 204, 0.8)'; } // Update uptime timer border const uptimeTimer = terminalElement.querySelector('.uptime-timer'); if (uptimeTimer) { uptimeTimer.style.borderColor = `rgba(0, 136, 204, 0.5)`; } // Update uptime separators const separators = terminalElement.querySelectorAll('.uptime-separator'); separators.forEach(sep => { sep.style.textShadow = '0 0 8px rgba(0, 136, 204, 0.8)'; }); // Update uptime title const uptimeTitle = terminalElement.querySelector('.uptime-title'); if (uptimeTitle) { uptimeTitle.style.textShadow = '0 0 5px rgba(0, 136, 204, 0.8)'; } // Update minimized view const miniLabel = terminalElement.querySelector('.mini-uptime-label'); if (miniLabel) { miniLabel.style.color = DEEPSEA_COLOR; } } else { // Reset to Bitcoin theme terminalElement.style.borderColor = BITCOIN_COLOR; terminalElement.style.color = BITCOIN_COLOR; terminalElement.style.boxShadow = `0 0 5px rgba(247, 147, 26, 0.3)`; // Update header border const headerElement = terminalElement.querySelector('.terminal-header'); if (headerElement) { headerElement.style.borderColor = BITCOIN_COLOR; } // Update terminal title const titleElement = terminalElement.querySelector('.terminal-title'); if (titleElement) { titleElement.style.color = BITCOIN_COLOR; titleElement.style.textShadow = '0 0 5px rgba(247, 147, 26, 0.8)'; } // Update uptime timer border const uptimeTimer = terminalElement.querySelector('.uptime-timer'); if (uptimeTimer) { uptimeTimer.style.borderColor = `rgba(247, 147, 26, 0.5)`; } // Update uptime separators const separators = terminalElement.querySelectorAll('.uptime-separator'); separators.forEach(sep => { sep.style.textShadow = '0 0 8px rgba(247, 147, 26, 0.8)'; }); // Update uptime title const uptimeTitle = terminalElement.querySelector('.uptime-title'); if (uptimeTitle) { uptimeTitle.style.textShadow = '0 0 5px rgba(247, 147, 26, 0.8)'; } // Update minimized view const miniLabel = terminalElement.querySelector('.mini-uptime-label'); if (miniLabel) { miniLabel.style.color = BITCOIN_COLOR; } } } /** * Listen for theme changes */ function setupThemeChangeListener() { // Listen for theme change events from localStorage window.addEventListener('storage', function (e) { if (e.key === 'useDeepSeaTheme') { applyThemeColor(); } }); } /** * Add dragging functionality to the terminal */ function addDraggingBehavior() { // Find the terminal element const terminal = document.getElementById('bitcoin-terminal') || document.querySelector('.bitcoin-terminal') || document.getElementById('retro-terminal-bar'); if (!terminal) { console.warn('Terminal element not found for drag behavior'); return; } let isDragging = false; let startX = 0; let startLeft = 0; // Function to handle mouse down (drag start) function handleMouseDown(e) { // Only enable dragging in desktop view if (window.innerWidth < 768) return; // Don't handle drag if clicking on controls if (e.target.closest('.terminal-dot')) return; isDragging = true; terminal.classList.add('dragging'); // Calculate start position startX = e.clientX; // Get current left position accounting for different possible styles const style = window.getComputedStyle(terminal); if (style.left !== 'auto') { startLeft = parseInt(style.left) || 0; } else { // Calculate from right if left is not set startLeft = window.innerWidth - (parseInt(style.right) || 0) - terminal.offsetWidth; } e.preventDefault(); // Prevent text selection } // Function to handle mouse move (dragging) function handleMouseMove(e) { if (!isDragging) return; // Calculate the horizontal movement - vertical stays fixed const deltaX = e.clientX - startX; let newLeft = startLeft + deltaX; // Constrain to window boundaries const maxLeft = window.innerWidth - terminal.offsetWidth; newLeft = Math.max(0, Math.min(newLeft, maxLeft)); // Update position - only horizontally along bottom terminal.style.left = newLeft + 'px'; terminal.style.right = 'auto'; // Remove right positioning terminal.style.transform = 'none'; // Remove transformations } // Function to handle mouse up (drag end) function handleMouseUp() { if (isDragging) { isDragging = false; terminal.classList.remove('dragging'); } } // Find the terminal header for dragging const terminalHeader = terminal.querySelector('.terminal-header'); if (terminalHeader) { terminalHeader.addEventListener('mousedown', handleMouseDown); } else { // If no header found, make the whole terminal draggable terminal.addEventListener('mousedown', handleMouseDown); } // Add mousemove and mouseup listeners to document document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); // Handle window resize to keep terminal visible window.addEventListener('resize', function () { if (window.innerWidth < 768) { // Reset position for mobile view terminal.style.left = '50%'; terminal.style.right = 'auto'; terminal.style.transform = 'translateX(-50%)'; } else { // Ensure terminal stays visible in desktop view const maxLeft = window.innerWidth - terminal.offsetWidth; const currentLeft = parseInt(window.getComputedStyle(terminal).left) || 0; if (currentLeft > maxLeft) { terminal.style.left = maxLeft + 'px'; } } }); } /** * Create and inject the retro terminal element into the DOM */ function createTerminalElement() { // Container element terminalElement = document.createElement('div'); terminalElement.id = 'bitcoin-terminal'; terminalElement.className = 'bitcoin-terminal'; // Terminal content - simplified for uptime-only terminalElement.innerHTML = `
SYSTEM MONITOR v.3
LIVE
00:00:00
UPTIME
00 H
:
00 M
:
00 S
UPTIME 00:00:00
`; // Append to body document.body.appendChild(terminalElement); // Add dragging behavior addDraggingBehavior(); // Cache element references uptimeElement = document.getElementById('uptime-timer'); // Check if terminal was previously collapsed if (localStorage.getItem('bitcoin_terminal_collapsed') === 'true') { terminalElement.classList.add('collapsed'); } // Add custom styles if not already present if (!document.getElementById('bitcoin-terminal-styles')) { addStyles(); } } /** * Add CSS styles for the terminal */ function addStyles() { // Use the currentThemeColor variable instead of hardcoded colors const styleElement = document.createElement('style'); styleElement.id = 'bitcoin-terminal-styles'; styleElement.textContent = ` /* Terminal Container */ .bitcoin-terminal { position: fixed; bottom: 20px; right: 20px; width: 230px; background-color: #000000; border: 1px solid ${currentThemeColor}; color: ${currentThemeColor}; font-family: 'VT323', monospace; z-index: 9999; overflow: hidden; padding: 8px; transition: all 0.3s ease; box-shadow: 0 0 5px rgba(${currentThemeColor === DEEPSEA_COLOR ? '0, 136, 204' : '247, 147, 26'}, 0.3); } /* Terminal Header */ .terminal-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #f7931a; padding-bottom: 5px; margin-bottom: 8px; cursor: pointer; /* Add pointer (hand) cursor on hover */ } /* Apply grabbing cursor during active drag */ .terminal-header:active, .bitcoin-terminal.dragging .terminal-header { cursor: grabbing; } .terminal-title { color: #f7931a; font-weight: bold; font-size: 1.1rem; text-shadow: 0 0 5px rgba(247, 147, 26, 0.8); animation: terminal-flicker 4s infinite; } /* Control Dots */ .terminal-controls { display: flex; gap: 5px; margin-left: 5px; } .terminal-dot { width: 8px; height: 8px; border-radius: 50%; background-color: #555; cursor: pointer; transition: background-color 0.3s; } .terminal-dot.minimize:hover { background-color: #ffcc00; } .terminal-dot.close:hover { background-color: #ff3b30; } /* Terminal Content */ .terminal-content { position: relative; } /* Status Row */ .status-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; } /* Status Indicator */ .status-indicator { display: flex; align-items: center; gap: 8px; font-size: 0.8rem; } .status-dot { width: 6px; height: 6px; border-radius: 50%; } .status-dot.connected { background-color: #32CD32; box-shadow: 0 0 5px #32CD32; animation: pulse 2s infinite; } .terminal-clock { font-size: 1rem; font-weight: bold; text-shadow: 0 0 5px rgba(247, 147, 26, 0.5); } /* Uptime Display - Modern Digital Clock Style (Horizontal) */ .uptime-timer { display: flex; flex-direction: column; align-items: center; padding: 5px; background-color: #111; border: 1px solid rgba(247, 147, 26, 0.5); margin-top: 5px; } .uptime-display { display: flex; justify-content: center; align-items: center; gap: 2px; margin-top: 5px; } .uptime-value { display: flex; align-items: baseline; } .uptime-number { font-size: 1.4rem; font-weight: bold; background-color: #000; padding: 2px 5px; border-radius: 3px; min-width: 32px; display: inline-block; text-align: center; letter-spacing: 2px; text-shadow: 0 0 8px rgba(247, 147, 26, 0.8); color: #dee2e6; } .uptime-label { font-size: 0.7rem; opacity: 0.7; margin-left: 2px; } .uptime-separator { font-size: 1.4rem; font-weight: bold; padding: 0 2px; text-shadow: 0 0 8px rgba(247, 147, 26, 0.8); } .uptime-title { font-size: 0.7rem; font-weight: bold; text-transform: uppercase; letter-spacing: 2px; text-shadow: 0 0 5px rgba(247, 147, 26, 0.8); margin-bottom: 3px; } /* Show button */ #bitcoin-terminal-show { position: fixed; bottom: 10px; right: 10px; background-color: #f7931a; color: #000; border: none; padding: 8px 12px; font-family: 'VT323', monospace; cursor: pointer; z-index: 9999; display: none; box-shadow: 0 0 10px rgba(247, 147, 26, 0.5); } /* CRT scanline effect */ .terminal-content::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: repeating-linear-gradient( 0deg, rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.15) 1px, transparent 1px, transparent 2px ); pointer-events: none; z-index: 1; } /* Minimized view styling */ .terminal-minimized { display: none; flex-direction: row; align-items: center; justify-content: space-between; padding: 4px 10px; background-color: #000; position: relative; } .terminal-minimized::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: repeating-linear-gradient( 0deg, rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.15) 1px, transparent 1px, transparent 2px ); pointer-events: none; z-index: 1; } .minimized-uptime { display: flex; flex-direction: column; align-items: center; position: relative; z-index: 2; } .mini-uptime-label { font-size: 0.6rem; font-weight: bold; text-transform: uppercase; letter-spacing: 1px; opacity: 0.7; margin-left: 45px; color: #f7931a; } #minimized-uptime-value { font-size: 0.9rem; font-weight: bold; text-shadow: 0 0 5px rgba(247, 147, 26, 0.5); margin-left: 45px; color: #dee2e6; } .minimized-status-dot { width: 6px; height: 6px; border-radius: 50%; margin-left: 10px; position: relative; z-index: 2; } /* Collapsed state */ .bitcoin-terminal.collapsed { width: auto; max-width: 500px; height: auto; padding: 5px; } .bitcoin-terminal.collapsed .terminal-content { display: none; } .bitcoin-terminal.collapsed .terminal-minimized { display: flex; } .bitcoin-terminal.collapsed .terminal-header { border-bottom: none; margin-bottom: 2px; padding-bottom: 2px; } /* Animations */ @keyframes pulse { 0%, 100% { opacity: 0.8; } 50% { opacity: 1; } } @keyframes terminal-flicker { 0% { opacity: 0.97; } 5% { opacity: 0.95; } 10% { opacity: 0.97; } 15% { opacity: 0.94; } 20% { opacity: 0.98; } 50% { opacity: 0.95; } 80% { opacity: 0.96; } 90% { opacity: 0.94; } 100% { opacity: 0.98; } } /* Media Queries */ @media (max-width: 768px) { .bitcoin-terminal { left: 50%; right: auto; transform: translateX(-50%); width: 90%; max-width: 320px; bottom: 10px; } .bitcoin-terminal.collapsed { width: auto; max-width: 300px; left: 50%; right: auto; transform: translateX(-50%); } } `; document.head.appendChild(styleElement); } /** * Update the terminal clock */ function updateClock() { try { const now = new Date(Date.now() + (serverTimeOffset || 0)); // Use the global timezone setting if available const timeZone = window.dashboardTimezone || 'America/Los_Angeles'; // Format the time in the configured timezone const timeString = now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true, timeZone: timeZone }); // Update clock in normal view const clockElement = document.getElementById('terminal-clock'); if (clockElement) { clockElement.textContent = timeString; } } catch (e) { console.error("BitcoinMinuteRefresh: Error updating clock:", e); } } /** * Update the uptime display */ function updateUptime() { if (serverStartTime) { try { const currentServerTime = Date.now() + serverTimeOffset; const diff = currentServerTime - serverStartTime; // Calculate hours, minutes, seconds const hours = Math.floor(diff / (1000 * 60 * 60)); const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((diff % (1000 * 60)) / 1000); // Update the main uptime display with digital clock style const uptimeHoursElement = document.getElementById('uptime-hours'); const uptimeMinutesElement = document.getElementById('uptime-minutes'); const uptimeSecondsElement = document.getElementById('uptime-seconds'); if (uptimeHoursElement) { uptimeHoursElement.textContent = String(hours).padStart(2, '0'); } if (uptimeMinutesElement) { uptimeMinutesElement.textContent = String(minutes).padStart(2, '0'); } if (uptimeSecondsElement) { uptimeSecondsElement.textContent = String(seconds).padStart(2, '0'); } // Update the minimized uptime display const minimizedUptimeElement = document.getElementById('minimized-uptime-value'); if (minimizedUptimeElement) { minimizedUptimeElement.textContent = `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`; } } catch (e) { console.error("BitcoinMinuteRefresh: Error updating uptime:", e); } } } /** * Notify other tabs that data has been refreshed */ function notifyRefresh() { const now = Date.now(); localStorage.setItem(STORAGE_KEY, now.toString()); localStorage.setItem('bitcoin_refresh_event', 'refresh-' + now); console.log("BitcoinMinuteRefresh: Notified other tabs of refresh at " + new Date(now).toISOString()); } /** * Initialize the uptime monitor */ function initialize(refreshFunc) { // Store the refresh callback refreshCallback = refreshFunc; // Get current theme status applyThemeColor(); // Create the terminal element if it doesn't exist if (!document.getElementById('bitcoin-terminal')) { createTerminalElement(); } else { // Get references to existing elements terminalElement = document.getElementById('bitcoin-terminal'); uptimeElement = document.getElementById('uptime-timer'); // Apply theme to existing element applyThemeColor(); } // Set up listener for theme changes setupThemeChangeListener(); // Try to get stored server time information try { serverTimeOffset = parseFloat(localStorage.getItem('serverTimeOffset') || '0'); serverStartTime = parseFloat(localStorage.getItem('serverStartTime') || '0'); } catch (e) { console.error("BitcoinMinuteRefresh: Error reading server time from localStorage:", e); } // Clear any existing intervals if (uptimeInterval) { clearInterval(uptimeInterval); } // Set up interval for updating clock and uptime display uptimeInterval = setInterval(function () { updateClock(); updateUptime(); }, 1000); // Update every second is sufficient for uptime display // Listen for storage events to sync across tabs window.removeEventListener('storage', handleStorageChange); window.addEventListener('storage', handleStorageChange); // Handle visibility changes document.removeEventListener('visibilitychange', handleVisibilityChange); document.addEventListener('visibilitychange', handleVisibilityChange); // Mark as initialized isInitialized = true; console.log("BitcoinMinuteRefresh: Initialized"); } /** * Handle storage changes for cross-tab synchronization */ function handleStorageChange(event) { if (event.key === 'bitcoin_refresh_event') { console.log("BitcoinMinuteRefresh: Detected refresh from another tab"); // If another tab refreshed, consider refreshing this one too // But don't refresh if it was just refreshed recently (5 seconds) const lastRefreshTime = parseInt(localStorage.getItem(STORAGE_KEY) || '0'); if (typeof refreshCallback === 'function' && Date.now() - lastRefreshTime > 5000) { refreshCallback(); } } else if (event.key === 'serverTimeOffset' || event.key === 'serverStartTime') { try { serverTimeOffset = parseFloat(localStorage.getItem('serverTimeOffset') || '0'); serverStartTime = parseFloat(localStorage.getItem('serverStartTime') || '0'); } catch (e) { console.error("BitcoinMinuteRefresh: Error reading updated server time:", e); } } } /** * Handle visibility changes */ function handleVisibilityChange() { if (!document.hidden) { console.log("BitcoinMinuteRefresh: Page became visible, updating"); // Update immediately when page becomes visible updateClock(); updateUptime(); // Check if we need to do a refresh based on time elapsed if (typeof refreshCallback === 'function') { const lastRefreshTime = parseInt(localStorage.getItem(STORAGE_KEY) || '0'); if (Date.now() - lastRefreshTime > 60000) { // More than a minute since last refresh refreshCallback(); } } } } /** * Update server time information */ function updateServerTime(timeOffset, startTime) { serverTimeOffset = timeOffset; serverStartTime = startTime; // Store in localStorage for cross-page sharing localStorage.setItem('serverTimeOffset', serverTimeOffset.toString()); localStorage.setItem('serverStartTime', serverStartTime.toString()); // Update the uptime immediately updateUptime(); console.log("BitcoinMinuteRefresh: Server time updated - offset:", serverTimeOffset, "ms"); } /** * Toggle terminal collapsed state */ function toggleTerminal() { if (!terminalElement) return; terminalElement.classList.toggle('collapsed'); localStorage.setItem('bitcoin_terminal_collapsed', terminalElement.classList.contains('collapsed')); } /** * Hide the terminal and show the restore button */ function hideTerminal() { if (!terminalElement) return; terminalElement.style.display = 'none'; // Create show button if it doesn't exist if (!document.getElementById('bitcoin-terminal-show')) { const showButton = document.createElement('button'); showButton.id = 'bitcoin-terminal-show'; showButton.textContent = 'Show Monitor'; showButton.onclick = showTerminal; document.body.appendChild(showButton); } document.getElementById('bitcoin-terminal-show').style.display = 'block'; } /** * Show the terminal and hide the restore button */ function showTerminal() { if (!terminalElement) return; terminalElement.style.display = 'block'; document.getElementById('bitcoin-terminal-show').style.display = 'none'; } // Public API return { initialize: initialize, notifyRefresh: notifyRefresh, updateServerTime: updateServerTime, toggleTerminal: toggleTerminal, hideTerminal: hideTerminal, showTerminal: showTerminal, updateTheme: applyThemeColor // <-- Add this new method }; })(); // Auto-initialize when document is ready document.addEventListener('DOMContentLoaded', function () { // Check if manualRefresh function exists in global scope if (typeof window.manualRefresh === 'function') { BitcoinMinuteRefresh.initialize(window.manualRefresh); } else { console.log("BitcoinMinuteRefresh: No refresh function found, will need to be initialized manually"); } // Update theme based on current setting setTimeout(() => BitcoinMinuteRefresh.updateTheme(), 100); });