/** * BitcoinMinuteRefresh.js - A minute-based refresh system tied to server uptime * * This module creates a Bitcoin-themed terminal that shows server uptime * and refreshes data only on minute boundaries for better synchronization. */ const BitcoinMinuteRefresh = (function () { // Constants const STORAGE_KEY = 'bitcoin_last_refresh_time'; // For cross-page sync // Private variables let terminalElement = null; let uptimeElement = null; let serverTimeOffset = 0; let serverStartTime = null; let uptimeInterval = null; let lastMinuteValue = -1; let isInitialized = false; let refreshCallback = null; /** * 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 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); // 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() { const styleElement = document.createElement('style'); styleElement.id = 'bitcoin-terminal-styles'; styleElement.textContent = ` /* Terminal Container */ .bitcoin-terminal { position: fixed; bottom: 20px; right: 20px; width: 340px; background-color: #000000; border: 1px solid #f7931a; // Changed from 2px to 1px color: #f7931a; font-family: 'VT323', monospace; z-index: 9999; overflow: hidden; padding: 8px; transition: all 0.3s ease; box-shadow: 0 0 5px rgba(247, 147, 26, 0.3); // Added to match card shadow } /* Terminal Header */ .terminal-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #f7931a; padding-bottom: 5px; margin-bottom: 8px; } .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); } /* Minute Progress Bar */ .minute-progress-container { margin-bottom: 12px; } .minute-labels { display: flex; justify-content: space-between; font-size: 0.7rem; margin-bottom: 2px; } .minute-progress-bar { width: 100%; height: 15px; background-color: #111; border: 1px solid #f7931a; position: relative; overflow: hidden; box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.8); } .minute-progress-inner { height: 100%; width: 0%; position: relative; overflow: hidden; } .minute-progress-fill { height: 100%; width: 100%; background: linear-gradient(90deg, #f7931a, #ffa500); transition: width 0.5s ease-out; } /* Scan line effect */ .scan-line { position: absolute; height: 2px; width: 100%; background-color: rgba(255, 255, 255, 0.7); animation: scan 2s linear infinite; box-shadow: 0 0 8px 1px rgba(255, 255, 255, 0.5); z-index: 2; will-change: transform; } /* Refresh status */ .refresh-status { font-size: 0.8rem; margin-top: 5px; text-align: center; 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: #f7931a; } .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; } /* Special effects */ .minute-progress-inner.near-refresh .minute-progress-fill { box-shadow: 0 0 15px #f7931a, 0 0 25px #f7931a; animation: pulse-brightness 1s infinite; } .minute-progress-inner.refresh-now .minute-progress-fill { animation: refresh-flash 1s forwards; } /* 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-clock { font-size: 1.1rem; font-weight: bold; text-shadow: 0 0 5px rgba(247, 147, 26, 0.5); position: relative; z-index: 2; } .minimized-separator { margin: 0 10px; color: rgba(247, 147, 26, 0.5); font-size: 1.1rem; position: relative; z-index: 2; } .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: #f7931a; } .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 scan { 0% { top: -2px; } 100% { top: 17px; } } @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; } } @keyframes pulse-brightness { 0%, 100% { filter: brightness(1); } 50% { filter: brightness(1.3); } } @keyframes refresh-flash { 0% { filter: brightness(1); background-color: #f7931a; } 10% { filter: brightness(1.8); background-color: #fff; } 20% { filter: brightness(1); background-color: #f7931a; } 30% { filter: brightness(1.8); background-color: #fff; } 40% { filter: brightness(1); background-color: #f7931a; } 100% { filter: brightness(1); background-color: #f7931a; } } /* 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%; // Changed from "left: auto" right: auto; // Changed from "right: 10px" transform: translateX(-50%); // Changed from "transform: none" } } `; document.head.appendChild(styleElement); } /** * Update the minute progress bar based on current seconds */ function updateMinuteProgress() { try { // Get current server time - keep this for other functions that might use it const now = new Date(Date.now() + (serverTimeOffset || 0)); // We need to keep track of minutes for other functionality const currentMinute = now.getMinutes(); // Update last minute value (keeping this for any other code that might rely on it) lastMinuteValue = currentMinute; // No progress bar updates or effects } catch (e) { console.error("BitcoinMinuteRefresh: Error updating progress bar:", e); } } /** * Update the terminal clock */ function updateClock() { try { const now = new Date(Date.now() + (serverTimeOffset || 0)); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); const timeString = `${hours}:${minutes}:${seconds}`; // Update both clocks (normal and minimized views) const clockElement = document.getElementById('terminal-clock'); if (clockElement) { clockElement.textContent = timeString; } const minimizedClockElement = document.getElementById('minimized-clock-value'); if (minimizedClockElement) { minimizedClockElement.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 document.getElementById('uptime-hours').textContent = String(hours).padStart(2, '0'); document.getElementById('uptime-minutes').textContent = String(minutes).padStart(2, '0'); document.getElementById('uptime-seconds').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')}`; } // NEW CODE: Update the dashboard's uptime display if it exists const dashboardUptimeElement = document.getElementById('uptimeTimer'); if (dashboardUptimeElement) { dashboardUptimeElement.innerHTML = `Uptime: ${String(hours).padStart(2, '0')}h ${String(minutes).padStart(2, '0')}m ${String(seconds).padStart(2, '0')}s`; } } 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 minute refresh system */ function initialize(refreshFunc) { // Store the refresh callback refreshCallback = refreshFunc; // 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'); } // NEW CODE: Check if dashboard uptime element exists const dashboardUptimeElement = document.getElementById('uptimeTimer'); if (dashboardUptimeElement) { console.log("BitcoinMinuteRefresh: Found dashboard uptime element, will sync with it"); } // 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 intervals for updating at 50ms precision for smooth animation uptimeInterval = setInterval(function () { updateClock(); updateUptime(); updateMinuteProgress(); }, 50); // 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); // Initialize last minute value const now = new Date(Date.now() + serverTimeOffset); lastMinuteValue = now.getMinutes(); // Log current server time details for debugging console.log(`BitcoinMinuteRefresh: Server time is ${now.toISOString()}, minute=${lastMinuteValue}, seconds=${now.getSeconds()}`); // 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(); updateMinuteProgress(); // 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(); updateMinuteProgress(); 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 }; })(); // Auto-initialize when document is ready if a refresh function is available in the global scope 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"); } });