diff --git a/static/css/theme-toggle.css b/static/css/theme-toggle.css new file mode 100644 index 0000000..b51f91f --- /dev/null +++ b/static/css/theme-toggle.css @@ -0,0 +1,146 @@ +/* Responsive Theme Toggle Button Styles */ +/* Common styles for the theme toggle button regardless of device */ +#themeToggle, +.theme-toggle-btn { + position: fixed; + z-index: 1000; + background: transparent; + border-width: 1px; + border-style: solid; + font-family: 'VT323', monospace; + transition: all 0.3s ease; + cursor: pointer; + white-space: nowrap; + text-transform: uppercase; + outline: none; + display: flex; + align-items: center; + justify-content: center; +} + +/* Default positioning and styling for desktop */ +@media screen and (min-width: 768px) { + #themeToggle, + .theme-toggle-btn { + top: 15px; + left: 15px; + padding: 6px 12px; + font-size: 14px; + border-radius: 3px; + letter-spacing: 0.5px; + } + + /* Add theme icon for desktop view */ + #themeToggle:before, + .theme-toggle-btn:before { + content: "☀/☾"; + margin-right: 5px; + font-size: 14px; + } + + /* Hover effects for desktop */ + #themeToggle:hover, + .theme-toggle-btn:hover { + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); + } +} + +/* Mobile-specific styling - MODIFIED to keep left positioning */ +@media screen and (max-width: 767px) { + #themeToggle, + .theme-toggle-btn { + top: 60px; + left: 10px; /* Keep on left side */ + padding: 8px; + font-size: 12px; + border-radius: 50%; + width: 40px; + height: 40px; + } + + /* Use just icon for mobile to save space */ + #themeToggle:before, + .theme-toggle-btn:before { + content: "☀/☾"; + margin-right: 0; + font-size: 16px; + } + + /* Hide text on mobile */ + #themeToggle span, + .theme-toggle-btn span { + display: none; + } + + /* Adjust position when in portrait mode on very small screens */ + @media screen and (max-height: 500px) { + #themeToggle, + .theme-toggle-btn { + top: 5px; + left: 5px; /* Keep on left side */ + width: 35px; + height: 35px; + font-size: 10px; + } + } +} + +/* The rest of the CSS remains unchanged */ +/* Active state for the button */ +#themeToggle:active, +.theme-toggle-btn:active { + transform: translateY(1px); + box-shadow: 0 0 2px rgba(0, 0, 0, 0.3); +} + +/* Bitcoin theme specific styling (orange) */ +body:not(.deepsea-theme) #themeToggle, +body:not(.deepsea-theme) .theme-toggle-btn { + color: #f2a900; + border-color: #f2a900; +} + + body:not(.deepsea-theme) #themeToggle:hover, + body:not(.deepsea-theme) .theme-toggle-btn:hover { + background-color: rgba(242, 169, 0, 0.1); + box-shadow: 0 4px 8px rgba(242, 169, 0, 0.3); + } + +/* DeepSea theme specific styling (blue) */ +body.deepsea-theme #themeToggle, +body.deepsea-theme .theme-toggle-btn { + color: #0088cc; + border-color: #0088cc; +} + + body.deepsea-theme #themeToggle:hover, + body.deepsea-theme .theme-toggle-btn:hover { + background-color: rgba(0, 136, 204, 0.1); + box-shadow: 0 4px 8px rgba(0, 136, 204, 0.3); + } + +/* Transition effect for smoother theme switching */ +#themeToggle, +.theme-toggle-btn, +#themeToggle:before, +.theme-toggle-btn:before { + transition: all 0.3s ease; +} + + /* Accessibility improvements */ + #themeToggle:focus, + .theme-toggle-btn:focus { + box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.3); + outline: none; + } + +body:not(.deepsea-theme) #themeToggle:focus, +body:not(.deepsea-theme) .theme-toggle-btn:focus { + box-shadow: 0 0 0 3px rgba(242, 169, 0, 0.3); +} + +body.deepsea-theme #themeToggle:focus, +body.deepsea-theme .theme-toggle-btn:focus { + box-shadow: 0 0 0 3px rgba(0, 136, 204, 0.3); +} diff --git a/static/js/BitcoinProgressBar.js b/static/js/BitcoinProgressBar.js index 7b72774..d4ca81a 100644 --- a/static/js/BitcoinProgressBar.js +++ b/static/js/BitcoinProgressBar.js @@ -2,11 +2,14 @@ * 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; @@ -16,6 +19,117 @@ const BitcoinMinuteRefresh = (function () { 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 @@ -200,6 +314,7 @@ const BitcoinMinuteRefresh = (function () { * 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 = ` @@ -210,14 +325,14 @@ const BitcoinMinuteRefresh = (function () { right: 20px; width: 230px; background-color: #000000; - border: 1px solid #f7931a; - color: #f7931a; + 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(247, 147, 26, 0.3); + box-shadow: 0 0 5px rgba(${currentThemeColor === DEEPSEA_COLOR ? '0, 136, 204' : '247, 147, 26'}, 0.3); } /* Terminal Header */ @@ -616,6 +731,9 @@ const BitcoinMinuteRefresh = (function () { // 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(); @@ -623,8 +741,14 @@ const BitcoinMinuteRefresh = (function () { // 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'); @@ -766,11 +890,12 @@ const BitcoinMinuteRefresh = (function () { updateServerTime: updateServerTime, toggleTerminal: toggleTerminal, hideTerminal: hideTerminal, - showTerminal: showTerminal + showTerminal: showTerminal, + updateTheme: applyThemeColor // <-- Add this new method }; })(); -// Auto-initialize when document is ready if a refresh function is available in the global scope +// Auto-initialize when document is ready document.addEventListener('DOMContentLoaded', function () { // Check if manualRefresh function exists in global scope if (typeof window.manualRefresh === 'function') { @@ -778,4 +903,7 @@ document.addEventListener('DOMContentLoaded', function () { } else { console.log("BitcoinMinuteRefresh: No refresh function found, will need to be initialized manually"); } + + // Update theme based on current setting + setTimeout(() => BitcoinMinuteRefresh.updateTheme(), 100); }); diff --git a/static/js/main.js b/static/js/main.js index e1fc580..2e6c14d 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -1,4 +1,7 @@ -"use strict"; +// Add this flag at the top of your file, outside the function +let isApplyingTheme = false; + +"use strict"; /** * ArrowIndicator - A clean implementation for managing metric value change indicators @@ -176,6 +179,11 @@ class ArrowIndicator { }); } + // Current theme affects arrow colors + const theme = getCurrentTheme(); + const upArrowColor = THEME.SHARED.GREEN; + const downArrowColor = THEME.SHARED.RED; + // Get normalized values and compare with previous metrics for (const key of metricKeys) { if (newMetrics[key] === undefined) continue; @@ -714,15 +722,16 @@ function handleVisibilityChange() { // Helper function to show connection issues to the user function showConnectionIssue(message) { + const theme = getCurrentTheme(); let $connectionStatus = $("#connectionStatus"); if (!$connectionStatus.length) { - $("body").append('
'); + $("body").append(``); $connectionStatus = $("#connectionStatus"); } $connectionStatus.html(` ${message}`).show(); - // Show manual refresh button when there are connection issues - $("#refreshButton").show(); + // Show manual refresh button with theme color + $("#refreshButton").css('background-color', theme.PRIMARY).show(); } // Helper function to hide connection issue message @@ -766,7 +775,7 @@ function manualRefresh() { }); } -// Initialize Chart.js with Unit Normalization +// Modify the initializeChart function to use blue colors for the chart function initializeChart() { try { const ctx = document.getElementById('trendGraph').getContext('2d'); @@ -780,10 +789,12 @@ function initializeChart() { return null; } + // Get the current theme colors + const theme = getCurrentTheme(); + // Check if Chart.js plugin is available const hasAnnotationPlugin = window['chartjs-plugin-annotation'] !== undefined; - // Inside the initializeChart function, modify the dataset configuration: return new Chart(ctx, { type: 'line', data: { @@ -796,29 +807,29 @@ function initializeChart() { const chart = context.chart; const { ctx, chartArea } = chart; if (!chartArea) { - return '#f7931a'; + return theme.PRIMARY; } // Create gradient for line const gradient = ctx.createLinearGradient(0, 0, 0, chartArea.bottom); - gradient.addColorStop(0, '#ffa64d'); // Lighter orange - gradient.addColorStop(1, '#f7931a'); // Bitcoin orange + gradient.addColorStop(0, theme.CHART.GRADIENT_START); + gradient.addColorStop(1, theme.CHART.GRADIENT_END); return gradient; }, backgroundColor: function (context) { const chart = context.chart; const { ctx, chartArea } = chart; if (!chartArea) { - return 'rgba(247,147,26,0.1)'; + return `rgba(${theme.PRIMARY_RGB}, 0.1)`; } // Create gradient for fill const gradient = ctx.createLinearGradient(0, 0, 0, chartArea.bottom); - gradient.addColorStop(0, 'rgba(255, 166, 77, 0.3)'); // Lighter orange with transparency - gradient.addColorStop(0.5, 'rgba(247, 147, 26, 0.2)'); // Bitcoin orange with medium transparency - gradient.addColorStop(1, 'rgba(247, 147, 26, 0.05)'); // Bitcoin orange with high transparency + gradient.addColorStop(0, `rgba(${theme.PRIMARY_RGB}, 0.3)`); + gradient.addColorStop(0.5, `rgba(${theme.PRIMARY_RGB}, 0.2)`); + gradient.addColorStop(1, `rgba(${theme.PRIMARY_RGB}, 0.05)`); return gradient; }, fill: true, - tension: 0.3, // Slightly increase tension for smoother curves + tension: 0.3, }] }, options: { @@ -848,10 +859,10 @@ function initializeChart() { y: { title: { display: true, - text: 'HASHRATE (TH/S)', // Already uppercase - color: '#f7931a', // Bitcoin orange + text: 'HASHRATE (TH/S)', + color: theme.PRIMARY, font: { - family: "'VT323', monospace", // Terminal font + family: "'VT323', monospace", size: 16, weight: 'bold' } @@ -905,7 +916,7 @@ function initializeChart() { plugins: { tooltip: { backgroundColor: 'rgba(0, 0, 0, 0.8)', - titleColor: '#f7931a', + titleColor: theme.PRIMARY, bodyColor: '#FFFFFF', titleFont: { family: "'VT323', monospace", @@ -937,10 +948,10 @@ function initializeChart() { type: 'line', yMin: 0, yMax: 0, - borderColor: '#ffd700', // Gold color + borderColor: theme.CHART.ANNOTATION, borderWidth: 3, borderDash: [8, 4], - shadowColor: 'rgba(255, 215, 0, 0.5)', + shadowColor: `rgba(${theme.PRIMARY_RGB}, 0.5)`, shadowBlur: 8, shadowOffsetX: 0, shadowOffsetY: 0, @@ -948,7 +959,7 @@ function initializeChart() { enabled: true, content: '24HR AVG: 0 TH/S', backgroundColor: 'rgba(0,0,0,0.8)', - color: '#ffd700', + color: theme.CHART.ANNOTATION, font: { family: "'VT323', monospace", size: 16, @@ -1237,20 +1248,21 @@ function updateChartWithNormalizedData(chart, data) { chart.data.datasets[0].data = [normalizedValue]; } - // If using 3hr average due to low hashrate device detection, add visual indicator + // In updateChartWithNormalizedData function if (useHashrate3hr) { // Add indicator text to the chart if (!chart.lowHashrateIndicator) { // Create the indicator element if it doesn't exist const graphContainer = document.getElementById('graphContainer'); if (graphContainer) { + const theme = getCurrentTheme(); const indicator = document.createElement('div'); indicator.id = 'lowHashrateIndicator'; indicator.style.position = 'absolute'; indicator.style.bottom = '10px'; indicator.style.right = '10px'; indicator.style.background = 'rgba(0,0,0,0.7)'; - indicator.style.color = '#f7931a'; + indicator.style.color = theme.PRIMARY; indicator.style.padding = '5px 10px'; indicator.style.borderRadius = '3px'; indicator.style.fontSize = '12px'; @@ -1261,6 +1273,8 @@ function updateChartWithNormalizedData(chart, data) { chart.lowHashrateIndicator = indicator; } } else { + // Update color based on current theme + chart.lowHashrateIndicator.style.color = getCurrentTheme().PRIMARY; // Show the indicator if it already exists chart.lowHashrateIndicator.style.display = 'block'; } @@ -1902,8 +1916,512 @@ function resetDashboardChart() { } } -// Document ready initialization +// Replace your entire function with this version +function applyDeepSeaTheme() { + // Check if we're already applying the theme to prevent recursion + if (isApplyingTheme) { + console.log("Theme application already in progress, avoiding recursion"); + return; + } + + // Set the guard flag + isApplyingTheme = true; + + try { + console.log("Applying DeepSea theme..."); + + // Create or update CSS variables for the DeepSea theme + const styleElement = document.createElement('style'); + styleElement.id = 'deepSeaThemeStyles'; // Give it an ID so we can check if it exists + + // Enhanced CSS with your requested changes + styleElement.textContent = ` + /* Base theme variables */ + :root { + --primary-color: #0088cc !important; + --bitcoin-orange: #0088cc !important; + --bitcoin-orange-rgb: 0, 136, 204 !important; + --bg-gradient: linear-gradient(135deg, #0a0a0a, #131b20) !important; + --accent-color: #00b3ff !important; + --header-bg: linear-gradient(to right, #0088cc, #005580) !important; + --card-header-bg: linear-gradient(to right, #0088cc, #006699) !important; + --progress-bar-color: #0088cc !important; + --link-color: #0088cc !important; + --link-hover-color: #00b3ff !important; + + /* Standardized text shadow values */ + --blue-text-shadow: 0 0 10px rgba(0, 136, 204, 0.8), 0 0 5px rgba(0, 136, 204, 0.5); + --yellow-text-shadow: 0 0 10px rgba(255, 215, 0, 0.8), 0 0 5px rgba(255, 215, 0, 0.5); + --green-text-shadow: 0 0 10px rgba(50, 205, 50, 0.8), 0 0 5px rgba(50, 205, 50, 0.5); + --red-text-shadow: 0 0 10px rgba(255, 85, 85, 0.8), 0 0 5px rgba(255, 85, 85, 0.5); + --white-text-shadow: 0 0 10px rgba(255, 255, 255, 0.8), 0 0 5px rgba(255, 255, 255, 0.5); + --cyan-text-shadow: 0 0 10px rgba(0, 255, 255, 0.8), 0 0 5px rgba(0, 255, 255, 0.5); + } + + /* Blue elements - main theme elements */ + .card-header, .card > .card-header, + .container-fluid .card > .card-header { + background: linear-gradient(to right, #0088cc, #006699) !important; + border-bottom: 1px solid #0088cc !important; + text-shadow: var(--blue-text-shadow) !important; + color: #fff !important; + } + + .card { + border: 1px solid #0088cc !important; + box-shadow: 0 0 5px rgba(0, 136, 204, 0.3) !important; + } + + /* Navigation and interface elements */ + .nav-link { + border: 1px solid #0088cc !important; + color: #0088cc !important; + } + + .nav-link:hover, .nav-link.active { + background-color: #0088cc !important; + color: #fff !important; + box-shadow: 0 0 10px rgba(0, 136, 204, 0.5) !important; + } + + #terminal-cursor { + background-color: #0088cc !important; + box-shadow: 0 0 5px rgba(0, 136, 204, 0.8) !important; + } + + #lastUpdated { + color: #0088cc !important; + } + + /* Chart and progress elements */ + .bitcoin-progress-inner { + background: linear-gradient(90deg, #0088cc, #00b3ff) !important; + } + + .bitcoin-progress-container { + border: 1px solid #0088cc !important; + box-shadow: 0 0 8px rgba(0, 136, 204, 0.5) !important; + } + + h1, .text-center h1 { + color: #0088cc !important; + text-shadow: var(--blue-text-shadow) !important; + } + + .nav-badge { + background-color: #0088cc !important; + } + + /* ===== COLOR SPECIFIC STYLING ===== */ + + /* YELLOW - SATOSHI EARNINGS & BTC PRICE */ + /* All Satoshi earnings in yellow with consistent text shadow */ + #daily_mined_sats, + #monthly_mined_sats, + #estimated_earnings_per_day_sats, + #estimated_earnings_next_block_sats, + #estimated_rewards_in_window_sats, + #btc_price, /* BTC Price in yellow */ + .card:contains('SATOSHI EARNINGS') span.metric-value { + color: #ffd700 !important; /* Bitcoin gold/yellow */ + text-shadow: var(--yellow-text-shadow) !important; + } + + /* More specific selectors for Satoshi values */ + span.metric-value[id$="_sats"] { + color: #ffd700 !important; + text-shadow: var(--yellow-text-shadow) !important; + } + + /* Retaining original yellow for specific elements */ + .est_time_to_payout:not(.green):not(.red) { + color: #ffd700 !important; + text-shadow: var(--yellow-text-shadow) !important; + } + + /* GREEN - POSITIVE USD VALUES */ + /* USD earnings that are positive should be green */ + .metric-value.green, + span.green, + #daily_revenue:not([style*="color: #ff"]), + #monthly_profit_usd:not([style*="color: #ff"]), + #daily_profit_usd:not([style*="color: #ff"]) { + color: #32CD32 !important; /* Lime green */ + text-shadow: var(--green-text-shadow) !important; + } + + /* Status indicators remain green */ + .status-green { + color: #32CD32 !important; + text-shadow: var(--green-text-shadow) !important; + } + + .online-dot { + background: #32CD32 !important; + box-shadow: 0 0 10px #32CD32, 0 0 20px #32CD32 !important; + } + + /* RED - NEGATIVE USD VALUES & WARNINGS */ + /* Red for negative values and warnings */ + .metric-value.red, + span.red, + .status-red, + #daily_power_cost { + color: #ff5555 !important; + text-shadow: var(--red-text-shadow) !important; + } + + .offline-dot { + background: #ff5555 !important; + box-shadow: 0 0 10px #ff5555, 0 0 20px #ff5555 !important; + } + + /* WHITE - Network stats and worker data */ + #block_number, + #difficulty, + #network_hashrate, + #pool_fees_percentage, + #workers_hashing, + #last_share, + #blocks_found, + #last_block_height { + color: #ffffff !important; + text-shadow: var(--white-text-shadow) !important; + } + + /* CYAN - Time ago in last block */ + #last_block_time { + color: #00ffff !important; /* Cyan */ + text-shadow: var(--cyan-text-shadow) !important; + } + + /* BLUE - Pool statistics */ + #pool_total_hashrate { + color: #0088cc !important; + text-shadow: var(--blue-text-shadow) !important; + } + + /* Hashrate values are white */ + #hashrate_24hr, + #hashrate_3hr, + #hashrate_10min, + #hashrate_60sec { + color: white !important; + text-shadow: var(--white-text-shadow) !important; + } + + /* Pool luck/efficiency colors - PRESERVE EXISTING */ + #pool_luck.very-lucky { + color: #32CD32 !important; /* Very lucky - bright green */ + text-shadow: var(--green-text-shadow) !important; + } + + #pool_luck.lucky { + color: #90EE90 !important; /* Lucky - light green */ + text-shadow: 0 0 10px rgba(144, 238, 144, 0.8), 0 0 5px rgba(144, 238, 144, 0.5) !important; + } + + #pool_luck.normal-luck { + color: #F0E68C !important; /* Normal - khaki */ + text-shadow: 0 0 10px rgba(240, 230, 140, 0.8), 0 0 5px rgba(240, 230, 140, 0.5) !important; + } + + #pool_luck.unlucky { + color: #ff5555 !important; /* Unlucky - red */ + text-shadow: var(--red-text-shadow) !important; + } + + /* Congrats message */ + #congratsMessage { + background: #0088cc !important; + box-shadow: 0 0 15px rgba(0, 136, 204, 0.7) !important; + } + + /* Animations */ + @keyframes waitingPulse { + 0%, 100% { box-shadow: 0 0 10px #0088cc, 0 0 15px #0088cc !important; opacity: 0.8; } + 50% { box-shadow: 0 0 20px #0088cc, 0 0 35px #0088cc !important; opacity: 1; } + } + + @keyframes glow { + 0%, 100% { box-shadow: 0 0 10px #0088cc, 0 0 15px #0088cc !important; } + 50% { box-shadow: 0 0 15px #0088cc, 0 0 25px #0088cc !important; } + } + `; + + // Check if our style element already exists + const existingStyle = document.getElementById('deepSeaThemeStyles'); + if (existingStyle) { + existingStyle.parentNode.removeChild(existingStyle); + } + + // Add our new style element to the head + document.head.appendChild(styleElement); + + // Update page title + document.title = document.title.replace("BTC-OS", "DeepSea"); + document.title = document.title.replace("Bitcoin", "DeepSea"); + + // Update header text + const headerElement = document.querySelector('h1'); + if (headerElement) { + headerElement.innerHTML = headerElement.innerHTML.replace("BTC-OS", "DeepSea"); + headerElement.innerHTML = headerElement.innerHTML.replace("BITCOIN", "DEEPSEA"); + } + + console.log("DeepSea theme applied with color adjustments"); + } finally { + // Always reset the guard flag when done, even if there's an error + isApplyingTheme = false; + } +} + +// Make the function accessible globally so main.js can check for it +window.applyDeepSeaTheme = applyDeepSeaTheme; + $(document).ready(function () { + // Apply theme based on stored preference - moved to beginning for better initialization + try { + const useDeepSea = localStorage.getItem('useDeepSeaTheme') === 'true'; + if (useDeepSea) { + applyDeepSeaTheme(); + } + // Setup theme change listener + setupThemeChangeListener(); + } catch (e) { + console.error("Error handling theme:", e); + } + + // Modify the initializeChart function to use blue colors for the chart + function initializeChart() { + try { + const ctx = document.getElementById('trendGraph').getContext('2d'); + if (!ctx) { + console.error("Could not find trend graph canvas"); + return null; + } + + if (!window.Chart) { + console.error("Chart.js not loaded"); + return null; + } + + // Get the current theme colors + const theme = getCurrentTheme(); + + // Check if Chart.js plugin is available + const hasAnnotationPlugin = window['chartjs-plugin-annotation'] !== undefined; + + return new Chart(ctx, { + type: 'line', + data: { + labels: [], + datasets: [{ + label: 'HASHRATE TREND (TH/s)', + data: [], + borderWidth: 2, + borderColor: function (context) { + const chart = context.chart; + const { ctx, chartArea } = chart; + if (!chartArea) { + return theme.PRIMARY; + } + // Create gradient for line + const gradient = ctx.createLinearGradient(0, 0, 0, chartArea.bottom); + gradient.addColorStop(0, theme.CHART.GRADIENT_START); + gradient.addColorStop(1, theme.CHART.GRADIENT_END); + return gradient; + }, + backgroundColor: function (context) { + const chart = context.chart; + const { ctx, chartArea } = chart; + if (!chartArea) { + return `rgba(${theme.PRIMARY_RGB}, 0.1)`; + } + // Create gradient for fill + const gradient = ctx.createLinearGradient(0, 0, 0, chartArea.bottom); + gradient.addColorStop(0, `rgba(${theme.PRIMARY_RGB}, 0.3)`); + gradient.addColorStop(0.5, `rgba(${theme.PRIMARY_RGB}, 0.2)`); + gradient.addColorStop(1, `rgba(${theme.PRIMARY_RGB}, 0.05)`); + return gradient; + }, + fill: true, + tension: 0.3, + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + animation: { + duration: 0 // Disable animations for better performance + }, + scales: { + x: { + display: true, + ticks: { + maxTicksLimit: 8, // Limit number of x-axis labels + maxRotation: 0, // Don't rotate labels + autoSkip: true, // Automatically skip some labels + color: '#FFFFFF', + font: { + family: "'VT323', monospace", // Terminal font + size: 14 + } + }, + grid: { + color: '#333333', + lineWidth: 0.5 + } + }, + y: { + title: { + display: true, + text: 'HASHRATE (TH/S)', + color: theme.PRIMARY, + font: { + family: "'VT323', monospace", + size: 16, + weight: 'bold' + } + }, + ticks: { + color: '#FFFFFF', + maxTicksLimit: 6, // Limit total number of ticks + precision: 1, // Control decimal precision + autoSkip: true, // Skip labels to prevent overcrowding + autoSkipPadding: 10, // Padding between skipped labels + font: { + family: "'VT323', monospace", // Terminal font + size: 14 + }, + callback: function (value) { + // For zero, just return 0 + if (value === 0) return '0'; + + // For very large values (1M+) + if (value >= 1000000) { + return (value / 1000000).toFixed(1) + 'M'; + } + // For large values (1K+) + else if (value >= 1000) { + return (value / 1000).toFixed(1) + 'K'; + } + // For values between 10 and 1000 + else if (value >= 10) { + return Math.round(value); + } + // For small values, limit decimal places + else if (value >= 1) { + return value.toFixed(1); + } + // For tiny values, use appropriate precision + else { + return value.toPrecision(2); + } + } + }, + grid: { + color: '#333333', + lineWidth: 0.5, + drawBorder: false, + zeroLineColor: '#555555', + zeroLineWidth: 1, + drawTicks: false + } + } + }, + plugins: { + tooltip: { + backgroundColor: 'rgba(0, 0, 0, 0.8)', + titleColor: theme.PRIMARY, + bodyColor: '#FFFFFF', + titleFont: { + family: "'VT323', monospace", + size: 16, + weight: 'bold' + }, + bodyFont: { + family: "'VT323', monospace", + size: 14 + }, + padding: 10, + cornerRadius: 0, + displayColors: false, + callbacks: { + title: function (tooltipItems) { + return tooltipItems[0].label.toUpperCase(); + }, + label: function (context) { + // Format tooltip values with appropriate unit + const value = context.raw; + return 'HASHRATE: ' + formatHashrateForDisplay(value).toUpperCase(); + } + } + }, + legend: { display: false }, + annotation: hasAnnotationPlugin ? { + annotations: { + averageLine: { + type: 'line', + yMin: 0, + yMax: 0, + borderColor: theme.CHART.ANNOTATION, + borderWidth: 3, + borderDash: [8, 4], + shadowColor: `rgba(${theme.PRIMARY_RGB}, 0.5)`, + shadowBlur: 8, + shadowOffsetX: 0, + shadowOffsetY: 0, + label: { + enabled: true, + content: '24HR AVG: 0 TH/S', + backgroundColor: 'rgba(0,0,0,0.8)', + color: theme.CHART.ANNOTATION, + font: { + family: "'VT323', monospace", + size: 16, + weight: 'bold' + }, + padding: { top: 4, bottom: 4, left: 8, right: 8 }, + borderRadius: 0, + position: 'start' + } + } + } + } : {} + } + } + }); + } catch (error) { + console.error("Error initializing chart:", error); + return null; + } + } + + // Add this function to the document ready section + function setupThemeChangeListener() { + // Listen for storage events to detect theme changes from other tabs/windows + window.addEventListener('storage', function (event) { + if (event.key === 'useDeepSeaTheme') { + // Update chart with new theme colors + if (trendChart) { + // Trigger chart update with new colors + trendChart.destroy(); + trendChart = initializeChart(); + updateChartWithNormalizedData(trendChart, latestMetrics); + } + + // Update other UI elements that depend on theme colors + updateRefreshButtonColor(); + + // Trigger a custom event that other components can listen to + $(document).trigger('themeChanged'); + } + }); + } + + setupThemeChangeListener(); + // Remove the existing refreshUptime container to avoid duplicates $('#refreshUptime').hide(); @@ -2046,6 +2564,14 @@ $(document).ready(function () { } } + // Update BitcoinProgressBar theme when theme changes + $(document).on('themeChanged', function () { + if (typeof BitcoinMinuteRefresh !== 'undefined' && + typeof BitcoinMinuteRefresh.updateTheme === 'function') { + BitcoinMinuteRefresh.updateTheme(); + } + }); + // Set up event source for SSE setupEventSource(); @@ -2053,8 +2579,8 @@ $(document).ready(function () { updateServerTime(); setInterval(updateServerTime, 30000); - // Add a manual refresh button for fallback - $("body").append(''); + // Update the manual refresh button color + $("body").append(''); $("#refreshButton").on("click", function () { $(this).text("Refreshing..."); diff --git a/static/js/theme.js b/static/js/theme.js new file mode 100644 index 0000000..b4dec2a --- /dev/null +++ b/static/js/theme.js @@ -0,0 +1,404 @@ +// Bitcoin Orange theme (default) +const BITCOIN_THEME = { + PRIMARY: '#f2a900', + PRIMARY_RGB: '242, 169, 0', + SHARED: { + GREEN: '#32CD32', + RED: '#ff5555', + YELLOW: '#ffd700' + }, + CHART: { + GRADIENT_START: '#f2a900', + GRADIENT_END: 'rgba(242, 169, 0, 0.2)', + ANNOTATION: '#ffd700' + } +}; + +// DeepSea theme (blue alternative) +const DEEPSEA_THEME = { + PRIMARY: '#0088cc', + PRIMARY_RGB: '0, 136, 204', + SHARED: { + GREEN: '#32CD32', + RED: '#ff5555', + YELLOW: '#ffd700' + }, + CHART: { + GRADIENT_START: '#0088cc', + GRADIENT_END: 'rgba(0, 136, 204, 0.2)', + ANNOTATION: '#00b3ff' + } +}; + +// Global theme constants +const THEME = { + BITCOIN: BITCOIN_THEME, + DEEPSEA: DEEPSEA_THEME, + SHARED: BITCOIN_THEME.SHARED +}; + +// Function to get the current theme based on localStorage setting +function getCurrentTheme() { + const useDeepSea = localStorage.getItem('useDeepSeaTheme') === 'true'; + return useDeepSea ? DEEPSEA_THEME : BITCOIN_THEME; +} + +// Make globals available +window.THEME = THEME; +window.getCurrentTheme = getCurrentTheme; + +// Use window-scoped variable to prevent conflicts +window.themeProcessing = false; + +// Fixed applyDeepSeaTheme function with recursion protection +function applyDeepSeaTheme() { + // Check if we're already applying the theme to prevent recursion + if (window.themeProcessing) { + console.log("Theme application already in progress, avoiding recursion"); + return; + } + + // Set the guard flag + window.themeProcessing = true; + + try { + console.log("Applying DeepSea theme..."); + + // Create or update CSS variables for the DeepSea theme + const styleElement = document.createElement('style'); + styleElement.id = 'deepSeaThemeStyles'; // Give it an ID so we can check if it exists + + // Enhanced CSS with your requested changes + styleElement.textContent = ` + /* Base theme variables */ + :root { + --primary-color: #0088cc !important; + --bitcoin-orange: #0088cc !important; + --bitcoin-orange-rgb: 0, 136, 204 !important; + --bg-gradient: linear-gradient(135deg, #0a0a0a, #131b20) !important; + --accent-color: #00b3ff !important; + --header-bg: linear-gradient(to right, #0088cc, #005580) !important; + --card-header-bg: linear-gradient(to right, #0088cc, #006699) !important; + --progress-bar-color: #0088cc !important; + --link-color: #0088cc !important; + --link-hover-color: #00b3ff !important; + + /* Standardized text shadow values */ + --blue-text-shadow: 0 0 10px rgba(0, 136, 204, 0.8), 0 0 5px rgba(0, 136, 204, 0.5); + --yellow-text-shadow: 0 0 10px rgba(255, 215, 0, 0.8), 0 0 5px rgba(255, 215, 0, 0.5); + --green-text-shadow: 0 0 10px rgba(50, 205, 50, 0.8), 0 0 5px rgba(50, 205, 50, 0.5); + --red-text-shadow: 0 0 10px rgba(255, 85, 85, 0.8), 0 0 5px rgba(255, 85, 85, 0.5); + --white-text-shadow: 0 0 10px rgba(255, 255, 255, 0.8), 0 0 5px rgba(255, 255, 255, 0.5); + --cyan-text-shadow: 0 0 10px rgba(0, 255, 255, 0.8), 0 0 5px rgba(0, 255, 255, 0.5); + } + + /* Blue elements - main theme elements */ + .card-header, .card > .card-header, + .container-fluid .card > .card-header { + background: linear-gradient(to right, #0088cc, #006699) !important; + border-bottom: 1px solid #0088cc !important; + text-shadow: var(--blue-text-shadow) !important; + color: #fff !important; + } + + .card { + border: 1px solid #0088cc !important; + box-shadow: 0 0 5px rgba(0, 136, 204, 0.3) !important; + } + + /* Navigation and interface elements */ + .nav-link { + border: 1px solid #0088cc !important; + color: #0088cc !important; + } + + .nav-link:hover, .nav-link.active { + background-color: #0088cc !important; + color: #fff !important; + box-shadow: 0 0 10px rgba(0, 136, 204, 0.5) !important; + } + + #terminal-cursor { + background-color: #0088cc !important; + box-shadow: 0 0 5px rgba(0, 136, 204, 0.8) !important; + } + + #lastUpdated { + color: #0088cc !important; + } + + /* Chart and progress elements */ + .bitcoin-progress-inner { + background: linear-gradient(90deg, #0088cc, #00b3ff) !important; + } + + .bitcoin-progress-container { + border: 1px solid #0088cc !important; + box-shadow: 0 0 8px rgba(0, 136, 204, 0.5) !important; + } + + h1, .text-center h1 { + color: #0088cc !important; + text-shadow: var(--blue-text-shadow) !important; + } + + .nav-badge { + background-color: #0088cc !important; + } + + /* Theme toggle button styling */ + #themeToggle, + button.theme-toggle, + .toggle-theme-btn { + background: transparent !important; + border: 1px solid #0088cc !important; + color: #0088cc !important; + transition: all 0.3s ease !important; + } + + #themeToggle:hover, + button.theme-toggle:hover, + .toggle-theme-btn:hover { + background-color: rgba(0, 136, 204, 0.1) !important; + box-shadow: 0 0 10px rgba(0, 136, 204, 0.3) !important; + } + + /* ===== COLOR SPECIFIC STYLING ===== */ + + /* YELLOW - SATOSHI EARNINGS & BTC PRICE */ + /* All Satoshi earnings in yellow with consistent text shadow */ + #daily_mined_sats, + #monthly_mined_sats, + #estimated_earnings_per_day_sats, + #estimated_earnings_next_block_sats, + #estimated_rewards_in_window_sats, + #btc_price, /* BTC Price in yellow */ + .card:contains('SATOSHI EARNINGS') span.metric-value { + color: #ffd700 !important; /* Bitcoin gold/yellow */ + text-shadow: var(--yellow-text-shadow) !important; + } + + /* More specific selectors for Satoshi values */ + span.metric-value[id$="_sats"] { + color: #ffd700 !important; + text-shadow: var(--yellow-text-shadow) !important; + } + + /* Retaining original yellow for specific elements */ + .est_time_to_payout:not(.green):not(.red) { + color: #ffd700 !important; + text-shadow: var(--yellow-text-shadow) !important; + } + + /* GREEN - POSITIVE USD VALUES */ + /* USD earnings that are positive should be green */ + .metric-value.green, + span.green, + #daily_revenue:not([style*="color: #ff"]), + #monthly_profit_usd:not([style*="color: #ff"]), + #daily_profit_usd:not([style*="color: #ff"]) { + color: #32CD32 !important; /* Lime green */ + text-shadow: var(--green-text-shadow) !important; + } + + /* Status indicators remain green */ + .status-green { + color: #32CD32 !important; + text-shadow: var(--green-text-shadow) !important; + } + + .online-dot { + background: #32CD32 !important; + box-shadow: 0 0 10px #32CD32, 0 0 20px #32CD32 !important; + } + + /* RED - NEGATIVE USD VALUES & WARNINGS */ + /* Red for negative values and warnings */ + .metric-value.red, + span.red, + .status-red, + #daily_power_cost { + color: #ff5555 !important; + text-shadow: var(--red-text-shadow) !important; + } + + .offline-dot { + background: #ff5555 !important; + box-shadow: 0 0 10px #ff5555, 0 0 20px #ff5555 !important; + } + + /* WHITE - Network stats and worker data */ + #block_number, + #difficulty, + #network_hashrate, + #pool_fees_percentage, + #workers_hashing, + #last_share, + #blocks_found, + #last_block_height { + color: #ffffff !important; + text-shadow: var(--white-text-shadow) !important; + } + + /* CYAN - Time ago in last block */ + #last_block_time { + color: #00ffff !important; /* Cyan */ + text-shadow: var(--cyan-text-shadow) !important; + } + + /* BLUE - Pool statistics */ + #pool_total_hashrate { + color: #0088cc !important; + text-shadow: var(--blue-text-shadow) !important; + } + + /* Hashrate values are white */ + #hashrate_24hr, + #hashrate_3hr, + #hashrate_10min, + #hashrate_60sec { + color: white !important; + text-shadow: var(--white-text-shadow) !important; + } + + /* Pool luck/efficiency colors - PRESERVE EXISTING */ + #pool_luck.very-lucky { + color: #32CD32 !important; /* Very lucky - bright green */ + text-shadow: var(--green-text-shadow) !important; + } + + #pool_luck.lucky { + color: #90EE90 !important; /* Lucky - light green */ + text-shadow: 0 0 10px rgba(144, 238, 144, 0.8), 0 0 5px rgba(144, 238, 144, 0.5) !important; + } + + #pool_luck.normal-luck { + color: #F0E68C !important; /* Normal - khaki */ + text-shadow: 0 0 10px rgba(240, 230, 140, 0.8), 0 0 5px rgba(240, 230, 140, 0.5) !important; + } + + #pool_luck.unlucky { + color: #ff5555 !important; /* Unlucky - red */ + text-shadow: var(--red-text-shadow) !important; + } + + /* Congrats message */ + #congratsMessage { + background: #0088cc !important; + box-shadow: 0 0 15px rgba(0, 136, 204, 0.7) !important; + } + + /* Animations */ + @keyframes waitingPulse { + 0%, 100% { box-shadow: 0 0 10px #0088cc, 0 0 15px #0088cc !important; opacity: 0.8; } + 50% { box-shadow: 0 0 20px #0088cc, 0 0 35px #0088cc !important; opacity: 1; } + } + + @keyframes glow { + 0%, 100% { box-shadow: 0 0 10px #0088cc, 0 0 15px #0088cc !important; } + 50% { box-shadow: 0 0 15px #0088cc, 0 0 25px #0088cc !important; } + } + `; + + // Check if our style element already exists + const existingStyle = document.getElementById('deepSeaThemeStyles'); + if (existingStyle) { + existingStyle.parentNode.removeChild(existingStyle); + } + + // Add our new style element to the head + document.head.appendChild(styleElement); + + // Update page title + document.title = document.title.replace("BTC-OS", "DeepSea"); + document.title = document.title.replace("Bitcoin", "DeepSea"); + + // Update header text + const headerElement = document.querySelector('h1'); + if (headerElement) { + headerElement.innerHTML = headerElement.innerHTML.replace("BTC-OS", "DeepSea"); + headerElement.innerHTML = headerElement.innerHTML.replace("BITCOIN", "DEEPSEA"); + } + + // Update theme toggle button + const themeToggle = document.getElementById('themeToggle'); + if (themeToggle) { + themeToggle.style.borderColor = '#0088cc'; + themeToggle.style.color = '#0088cc'; + } + + console.log("DeepSea theme applied with color adjustments"); + } finally { + // Always reset the guard flag when done, even if there's an error + window.themeProcessing = false; + } +} + +// Make the function accessible globally +window.applyDeepSeaTheme = applyDeepSeaTheme; + +// Toggle theme with hard page refresh +function toggleTheme() { + const useDeepSea = localStorage.getItem('useDeepSeaTheme') !== 'true'; + + // Save the new theme preference + saveThemePreference(useDeepSea); + + // Show a brief loading message to indicate theme change is happening + const loadingMessage = document.createElement('div'); + loadingMessage.style.position = 'fixed'; + loadingMessage.style.top = '0'; + loadingMessage.style.left = '0'; + loadingMessage.style.width = '100%'; + loadingMessage.style.height = '100%'; + loadingMessage.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'; + loadingMessage.style.display = 'flex'; + loadingMessage.style.justifyContent = 'center'; + loadingMessage.style.alignItems = 'center'; + loadingMessage.style.zIndex = '9999'; + loadingMessage.style.color = useDeepSea ? '#0088cc' : '#f2a900'; + loadingMessage.style.fontFamily = "'VT323', monospace"; + loadingMessage.style.fontSize = '24px'; + loadingMessage.innerHTML = 'LAST UPDATED: {{ current_time }}
{% endblock %} @@ -57,6 +79,11 @@ {% block congrats_message %} {% endblock %} + + +