Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Solana Transaction Tracker</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| .gradient-bg { | |
| background: linear-gradient(135deg, #00ffbd 0%, #0088ff 100%); | |
| } | |
| .transaction-card:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1); | |
| } | |
| .pulse { | |
| animation: pulse 2s infinite; | |
| } | |
| @keyframes pulse { | |
| 0% { opacity: 1; } | |
| 50% { opacity: 0.5; } | |
| 100% { opacity: 1; } | |
| } | |
| .address-chip { | |
| font-family: monospace; | |
| background-color: rgba(0, 136, 255, 0.1); | |
| border-left: 3px solid #0088ff; | |
| } | |
| .solana-gradient { | |
| background: linear-gradient(135deg, #00ffbd 0%, #0088ff 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen"> | |
| <div class="gradient-bg text-white py-6 px-4 shadow-lg"> | |
| <div class="container mx-auto"> | |
| <div class="flex justify-between items-center"> | |
| <h1 class="text-3xl font-bold flex items-center"> | |
| <i class="fas fa-wallet mr-3"></i> | |
| <span class="solana-gradient">Solana Tracker</span> | |
| </h1> | |
| <div class="flex items-center space-x-4"> | |
| <div class="bg-white bg-opacity-20 px-4 py-2 rounded-full"> | |
| <span id="current-time" class="font-mono"></span> | |
| </div> | |
| <button id="refresh-btn" class="bg-white text-blue-900 px-4 py-2 rounded-full hover:bg-blue-100 transition"> | |
| <i class="fas fa-sync-alt mr-2"></i> Refresh | |
| </button> | |
| </div> | |
| </div> | |
| <p class="mt-2 opacity-90">Tracking transactions for Solana address</p> | |
| <div class="mt-3 px-4 py-2 rounded-md address-chip inline-flex items-center"> | |
| <span>2HZdRs4Cxkg1WbiPxPZcramcEPo22dzeemMAYZhZpump</span> | |
| <button id="copy-address" class="ml-2 text-blue-200 hover:text-white"> | |
| <i class="fas fa-copy"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="container mx-auto px-4 py-8"> | |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> | |
| <!-- Left Column --> | |
| <div class="lg:col-span-2 space-y-6"> | |
| <!-- Summary Cards --> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4"> | |
| <div class="bg-white rounded-xl shadow p-6"> | |
| <div class="flex justify-between items-start"> | |
| <div> | |
| <p class="text-gray-500">Total Inflow</p> | |
| <h3 class="text-2xl font-bold mt-1 text-green-600" id="total-inflow">0.00 SOL</h3> | |
| </div> | |
| <div class="bg-green-100 p-3 rounded-full text-green-600"> | |
| <i class="fas fa-arrow-down"></i> | |
| </div> | |
| </div> | |
| <p class="text-sm text-gray-500 mt-3" id="inflow-change">+0.00% today</p> | |
| </div> | |
| <div class="bg-white rounded-xl shadow p-6"> | |
| <div class="flex justify-between items-start"> | |
| <div> | |
| <p class="text-gray-500">Total Outflow</p> | |
| <h3 class="text-2xl font-bold mt-1 text-red-600" id="total-outflow">0.00 SOL</h3> | |
| </div> | |
| <div class="bg-red-100 p-3 rounded-full text-red-600"> | |
| <i class="fas fa-arrow-up"></i> | |
| </div> | |
| </div> | |
| <p class="text-sm text-gray-500 mt-3" id="outflow-change">+0.00% today</p> | |
| </div> | |
| <div class="bg-white rounded-xl shadow p-6"> | |
| <div class="flex justify-between items-start"> | |
| <div> | |
| <p class="text-gray-500">Balance</p> | |
| <h3 class="text-2xl font-bold mt-1 text-blue-600" id="net-flow">0.00 SOL</h3> | |
| </div> | |
| <div class="bg-blue-100 p-3 rounded-full text-blue-600"> | |
| <i class="fas fa-coins"></i> | |
| </div> | |
| </div> | |
| <p class="text-sm text-gray-500 mt-3" id="net-change">$0.00 USD</p> | |
| </div> | |
| </div> | |
| <!-- Chart --> | |
| <div class="bg-white rounded-xl shadow p-6"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <h2 class="text-xl font-semibold">Transaction History</h2> | |
| <div class="flex space-x-2"> | |
| <button class="time-btn active bg-blue-100 text-blue-700 px-3 py-1 rounded-full text-sm" data-range="7">7D</button> | |
| <button class="time-btn bg-gray-100 text-gray-700 px-3 py-1 rounded-full text-sm" data-range="30">30D</button> | |
| <button class="time-btn bg-gray-100 text-gray-700 px-3 py-1 rounded-full text-sm" data-range="90">90D</button> | |
| </div> | |
| </div> | |
| <div class="h-80"> | |
| <canvas id="flowChart"></canvas> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Right Column --> | |
| <div class="space-y-6"> | |
| <!-- Address Info --> | |
| <div class="bg-white rounded-xl shadow p-6"> | |
| <h2 class="text-xl font-semibold mb-4">Wallet Info</h2> | |
| <div class="space-y-3"> | |
| <div class="flex justify-between"> | |
| <span class="text-gray-500">Network</span> | |
| <span class="font-medium">Solana Mainnet</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-gray-500">First Seen</span> | |
| <span class="font-medium" id="first-seen">Loading...</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-gray-500">Transactions</span> | |
| <span class="font-medium" id="tx-count">0</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-gray-500">SOL Price</span> | |
| <span class="font-medium" id="sol-price">$0.00</span> | |
| </div> | |
| </div> | |
| <div class="mt-4 pt-4 border-t border-gray-100"> | |
| <a href="https://solscan.io/account/2HZdRs4Cxkg1WbiPxPZcramcEPo22dzeemMAYZhZpump" target="_blank" class="w-full flex items-center justify-center bg-gray-100 text-gray-800 py-2 px-4 rounded-lg hover:bg-gray-200 transition"> | |
| <i class="fas fa-external-link-alt mr-2"></i> View on Solscan | |
| </a> | |
| </div> | |
| </div> | |
| <!-- Recent Transactions --> | |
| <div class="bg-white rounded-xl shadow p-6"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-xl font-semibold">Recent Transactions</h2> | |
| <div class="text-sm text-gray-500" id="last-updated">Just now</div> | |
| </div> | |
| <div class="space-y-3" id="transactions-list"> | |
| <!-- Transactions will be added here --> | |
| <div class="text-center py-8 text-gray-400"> | |
| <div class="pulse"> | |
| <i class="fas fa-spinner fa-spin text-4xl mb-2"></i> | |
| <p>Loading transactions...</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Transaction Details Modal --> | |
| <div id="tx-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden"> | |
| <div class="bg-white rounded-xl p-6 w-full max-w-md max-h-[90vh] overflow-y-auto"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="text-xl font-semibold">Transaction Details</h3> | |
| <button id="close-tx-modal" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div id="tx-details-content"> | |
| <!-- Transaction details will be loaded here --> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Initialize variables | |
| const solanaAddress = "2HZdRs4Cxkg1WbiPxPZcramcEPo22dzeemMAYZhZpump"; | |
| let transactions = []; | |
| let chart; | |
| let chartRange = 7; | |
| let solPrice = 0; | |
| // DOM Elements | |
| const refreshBtn = document.getElementById('refresh-btn'); | |
| const transactionsList = document.getElementById('transactions-list'); | |
| const timeButtons = document.querySelectorAll('.time-btn'); | |
| const copyAddressBtn = document.getElementById('copy-address'); | |
| const txModal = document.getElementById('tx-modal'); | |
| const closeTxModalBtn = document.getElementById('close-tx-modal'); | |
| // Initialize the app | |
| document.addEventListener('DOMContentLoaded', function() { | |
| updateCurrentTime(); | |
| setInterval(updateCurrentTime, 1000); | |
| // Initialize chart | |
| initChart(); | |
| // Load transactions | |
| loadTransactions(); | |
| fetchSolPrice(); | |
| // Set up event listeners | |
| refreshBtn.addEventListener('click', function() { | |
| this.classList.add('animate-spin'); | |
| loadTransactions(); | |
| fetchSolPrice(); | |
| setTimeout(() => { | |
| this.classList.remove('animate-spin'); | |
| }, 1000); | |
| }); | |
| copyAddressBtn.addEventListener('click', function() { | |
| navigator.clipboard.writeText(solanaAddress); | |
| const originalIcon = this.innerHTML; | |
| this.innerHTML = '<i class="fas fa-check"></i>'; | |
| setTimeout(() => { | |
| this.innerHTML = originalIcon; | |
| }, 2000); | |
| }); | |
| timeButtons.forEach(btn => { | |
| btn.addEventListener('click', function() { | |
| timeButtons.forEach(b => { | |
| b.classList.remove('active', 'bg-blue-100', 'text-blue-700'); | |
| b.classList.add('bg-gray-100', 'text-gray-700'); | |
| }); | |
| this.classList.add('active', 'bg-blue-100', 'text-blue-700'); | |
| this.classList.remove('bg-gray-100', 'text-gray-700'); | |
| chartRange = parseInt(this.dataset.range); | |
| updateChart(); | |
| }); | |
| }); | |
| closeTxModalBtn.addEventListener('click', () => { | |
| txModal.classList.add('hidden'); | |
| }); | |
| }); | |
| // Update current time | |
| function updateCurrentTime() { | |
| const now = new Date(); | |
| const options = { | |
| weekday: 'short', | |
| year: 'numeric', | |
| month: 'short', | |
| day: 'numeric', | |
| hour: '2-digit', | |
| minute: '2-digit', | |
| second: '2-digit' | |
| }; | |
| document.getElementById('current-time').textContent = now.toLocaleDateString('en-US', options); | |
| document.getElementById('last-updated').textContent = 'Updated: ' + now.toLocaleTimeString(); | |
| } | |
| // Load transactions from Solana API | |
| async function loadTransactions() { | |
| try { | |
| // Show loading state | |
| transactionsList.innerHTML = ` | |
| <div class="text-center py-8 text-gray-400"> | |
| <div class="pulse"> | |
| <i class="fas fa-spinner fa-spin text-4xl mb-2"></i> | |
| <p>Loading transactions...</p> | |
| </div> | |
| </div> | |
| `; | |
| // In a real app, you would fetch from Solana RPC or indexer API | |
| // For this demo, we'll use mock data | |
| const mockTransactions = generateMockTransactions(); | |
| transactions = mockTransactions; | |
| updateUI(); | |
| updateChart(); | |
| // Update first seen date | |
| if (transactions.length > 0) { | |
| const firstTxDate = new Date(transactions[transactions.length - 1].date); | |
| document.getElementById('first-seen').textContent = firstTxDate.toLocaleDateString(); | |
| } | |
| document.getElementById('tx-count').textContent = transactions.length; | |
| } catch (error) { | |
| console.error('Error loading transactions:', error); | |
| transactionsList.innerHTML = ` | |
| <div class="text-center py-8 text-red-400"> | |
| <i class="fas fa-exclamation-triangle text-4xl mb-2"></i> | |
| <p>Failed to load transactions</p> | |
| <button onclick="loadTransactions()" class="mt-2 text-blue-600 hover:text-blue-800"> | |
| <i class="fas fa-redo mr-1"></i> Retry | |
| </button> | |
| </div> | |
| `; | |
| } | |
| } | |
| // Generate mock transactions for demo | |
| function generateMockTransactions() { | |
| const mockTxs = []; | |
| const now = new Date(); | |
| const types = ['inflow', 'outflow']; | |
| const reasons = [ | |
| 'Token swap', | |
| 'NFT purchase', | |
| 'Staking reward', | |
| 'Airdrop', | |
| 'Payment received', | |
| 'Withdrawal', | |
| 'Deposit', | |
| 'Token transfer' | |
| ]; | |
| // Generate transactions for the past 90 days | |
| for (let i = 0; i < 30; i++) { | |
| const daysAgo = Math.floor(Math.random() * 90); | |
| const date = new Date(now); | |
| date.setDate(date.getDate() - daysAgo); | |
| const type = types[Math.floor(Math.random() * types.length)]; | |
| const amount = parseFloat((Math.random() * 10).toFixed(4)); | |
| const reason = reasons[Math.floor(Math.random() * reasons.length)]; | |
| const txHash = generateRandomHash(); | |
| mockTxs.push({ | |
| id: txHash, | |
| type: type, | |
| amount: amount, | |
| date: date.toISOString(), | |
| reason: reason, | |
| sender: type === 'inflow' ? generateRandomAddress() : solanaAddress, | |
| receiver: type === 'inflow' ? solanaAddress : generateRandomAddress(), | |
| fee: parseFloat((Math.random() * 0.001).toFixed(6)) | |
| }); | |
| } | |
| // Sort by date | |
| return mockTxs.sort((a, b) => new Date(b.date) - new Date(a.date)); | |
| } | |
| // Generate random Solana address | |
| function generateRandomAddress() { | |
| const chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; | |
| let result = ''; | |
| for (let i = 0; i < 44; i++) { | |
| result += chars.charAt(Math.floor(Math.random() * chars.length)); | |
| } | |
| return result; | |
| } | |
| // Generate random transaction hash | |
| function generateRandomHash() { | |
| const chars = '0123456789abcdef'; | |
| let result = ''; | |
| for (let i = 0; i < 64; i++) { | |
| result += chars.charAt(Math.floor(Math.random() * chars.length)); | |
| } | |
| return result; | |
| } | |
| // Fetch current SOL price (simulated) | |
| async function fetchSolPrice() { | |
| try { | |
| // Simulate API call with random variation | |
| const basePrice = 150; // Base SOL price | |
| const variation = (Math.random() * 30) - 15; // +/- $15 | |
| solPrice = parseFloat((basePrice + variation).toFixed(2)); | |
| document.getElementById('sol-price').textContent = `$${solPrice}`; | |
| // Update balance in USD | |
| const netFlowElement = document.getElementById('net-flow'); | |
| const netFlow = parseFloat(netFlowElement.textContent); | |
| document.getElementById('net-change').textContent = `$${(netFlow * solPrice).toFixed(2)} USD`; | |
| } catch (error) { | |
| console.error('Error fetching SOL price:', error); | |
| } | |
| } | |
| // Update all UI elements | |
| function updateUI() { | |
| updateTransactionSummary(); | |
| updateTransactionsList(); | |
| } | |
| // Update transaction summary cards | |
| function updateTransactionSummary() { | |
| const totalInflow = transactions | |
| .filter(t => t.type === 'inflow') | |
| .reduce((sum, t) => sum + t.amount, 0); | |
| const totalOutflow = transactions | |
| .filter(t => t.type === 'outflow') | |
| .reduce((sum, t) => sum + t.amount, 0); | |
| const netFlow = totalInflow - totalOutflow; | |
| document.getElementById('total-inflow').textContent = `${totalInflow.toFixed(4)} SOL`; | |
| document.getElementById('total-outflow').textContent = `${totalOutflow.toFixed(4)} SOL`; | |
| document.getElementById('net-flow').textContent = `${netFlow.toFixed(4)} SOL`; | |
| // Update USD values if price is available | |
| if (solPrice > 0) { | |
| document.getElementById('net-change').textContent = `$${(netFlow * solPrice).toFixed(2)} USD`; | |
| } | |
| // Random changes for demo purposes | |
| const inflowChange = (Math.random() * 5).toFixed(2); | |
| const outflowChange = (Math.random() * 5).toFixed(2); | |
| document.getElementById('inflow-change').textContent = `+${inflowChange}% today`; | |
| document.getElementById('outflow-change').textContent = `+${outflowChange}% today`; | |
| } | |
| // Update transactions list | |
| function updateTransactionsList() { | |
| if (transactions.length === 0) { | |
| transactionsList.innerHTML = ` | |
| <div class="text-center py-8 text-gray-400"> | |
| <i class="fas fa-exchange-alt text-4xl mb-2"></i> | |
| <p>No transactions found</p> | |
| </div> | |
| `; | |
| return; | |
| } | |
| // Show only the most recent 10 transactions | |
| const recentTransactions = transactions.slice(0, 10); | |
| transactionsList.innerHTML = recentTransactions.map(tx => ` | |
| <div class="transaction-card bg-white border border-gray-100 rounded-lg p-4 hover:shadow-md transition cursor-pointer" onclick="showTxDetails('${tx.id}')"> | |
| <div class="flex justify-between items-center"> | |
| <div class="flex items-center"> | |
| <div class="${tx.type === 'inflow' ? 'bg-green-100 text-green-600' : 'bg-red-100 text-red-600'} p-2 rounded-full mr-3"> | |
| <i class="fas ${tx.type === 'inflow' ? 'fa-arrow-down' : 'fa-arrow-up'}"></i> | |
| </div> | |
| <div> | |
| <p class="font-medium">${tx.reason}</p> | |
| <p class="text-sm text-gray-500">${formatDate(tx.date)}</p> | |
| </div> | |
| </div> | |
| <div class="text-right"> | |
| <p class="${tx.type === 'inflow' ? 'text-green-600' : 'text-red-600'} font-semibold"> | |
| ${tx.type === 'inflow' ? '+' : '-'}${tx.amount.toFixed(4)} | |
| </p> | |
| <p class="text-xs text-gray-500">SOL</p> | |
| </div> | |
| </div> | |
| </div> | |
| `).join(''); | |
| } | |
| // Show transaction details modal | |
| function showTxDetails(txId) { | |
| const tx = transactions.find(t => t.id === txId); | |
| if (!tx) return; | |
| const txDetailsContent = document.getElementById('tx-details-content'); | |
| txDetailsContent.innerHTML = ` | |
| <div class="space-y-4"> | |
| <div class="flex justify-between"> | |
| <span class="text-gray-500">Status</span> | |
| <span class="font-medium text-green-600">Confirmed</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-gray-500">Date</span> | |
| <span class="font-medium">${formatDate(tx.date)}</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-gray-500">Type</span> | |
| <span class="font-medium ${tx.type === 'inflow' ? 'text-green-600' : 'text-red-600'}"> | |
| ${tx.type === 'inflow' ? 'Incoming' : 'Outgoing'} Transfer | |
| </span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-gray-500">Amount</span> | |
| <span class="font-medium">${tx.amount.toFixed(4)} SOL ($${(tx.amount * solPrice).toFixed(2)})</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span class="text-gray-500">Transaction Fee</span> | |
| <span class="font-medium">${tx.fee.toFixed(6)} SOL</span> | |
| </div> | |
| <div> | |
| <p class="text-gray-500 mb-1">Description</p> | |
| <p class="font-medium">${tx.reason}</p> | |
| </div> | |
| <div class="${tx.type === 'inflow' ? 'block' : 'hidden'}"> | |
| <p class="text-gray-500 mb-1">From</p> | |
| <p class="font-mono text-sm bg-gray-100 p-2 rounded break-all">${tx.sender}</p> | |
| </div> | |
| <div class="${tx.type === 'outflow' ? 'block' : 'hidden'}"> | |
| <p class="text-gray-500 mb-1">To</p> | |
| <p class="font-mono text-sm bg-gray-100 p-2 rounded break-all">${tx.receiver}</p> | |
| </div> | |
| <div class="pt-4 border-t border-gray-100"> | |
| <a href="https://solscan.io/tx/${tx.id}" target="_blank" class="w-full flex items-center justify-center bg-blue-100 text-blue-800 py-2 px-4 rounded-lg hover:bg-blue-200 transition"> | |
| <i class="fas fa-external-link-alt mr-2"></i> View on Solscan | |
| </a> | |
| </div> | |
| </div> | |
| `; | |
| txModal.classList.remove('hidden'); | |
| } | |
| // Initialize chart | |
| function initChart() { | |
| const ctx = document.getElementById('flowChart').getContext('2d'); | |
| chart = new Chart(ctx, { | |
| type: 'bar', | |
| data: { | |
| labels: [], | |
| datasets: [ | |
| { | |
| label: 'Inflow', | |
| data: [], | |
| backgroundColor: '#10B981', | |
| borderRadius: 6 | |
| }, | |
| { | |
| label: 'Outflow', | |
| data: [], | |
| backgroundColor: '#EF4444', | |
| borderRadius: 6 | |
| } | |
| ] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { | |
| legend: { | |
| position: 'top', | |
| }, | |
| tooltip: { | |
| callbacks: { | |
| label: function(context) { | |
| return `${context.dataset.label}: ${context.raw.toFixed(4)} SOL`; | |
| } | |
| } | |
| } | |
| }, | |
| scales: { | |
| x: { | |
| grid: { | |
| display: false | |
| } | |
| }, | |
| y: { | |
| beginAtZero: true, | |
| ticks: { | |
| callback: function(value) { | |
| return value.toFixed(2); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| }); | |
| updateChart(); | |
| } | |
| // Update chart data | |
| function updateChart() { | |
| const now = new Date(); | |
| const pastDate = new Date(); | |
| pastDate.setDate(now.getDate() - chartRange); | |
| // Generate labels for the selected range | |
| const labels = []; | |
| const days = chartRange; | |
| for (let i = days; i >= 0; i--) { | |
| const date = new Date(); | |
| date.setDate(date.getDate() - i); | |
| labels.push(formatChartDate(date)); | |
| } | |
| // Initialize data arrays with zeros | |
| const inflowData = new Array(labels.length).fill(0); | |
| const outflowData = new Array(labels.length).fill(0); | |
| // Populate data arrays | |
| transactions.forEach(tx => { | |
| const txDate = new Date(tx.date); | |
| if (txDate >= pastDate) { | |
| const dayDiff = Math.floor((now - txDate) / (1000 * 60 * 60 * 24)); | |
| const index = labels.length - 1 - dayDiff; | |
| if (index >= 0 && index < labels.length) { | |
| if (tx.type === 'inflow') { | |
| inflowData[index] += tx.amount; | |
| } else { | |
| outflowData[index] += tx.amount; | |
| } | |
| } | |
| } | |
| }); | |
| // Update chart | |
| chart.data.labels = labels; | |
| chart.data.datasets[0].data = inflowData; | |
| chart.data.datasets[1].data = outflowData; | |
| chart.update(); | |
| } | |
| // Helper functions | |
| function formatDate(dateString) { | |
| const date = new Date(dateString); | |
| return date.toLocaleString('en-US', { | |
| month: 'short', | |
| day: 'numeric', | |
| year: 'numeric', | |
| hour: '2-digit', | |
| minute: '2-digit' | |
| }); | |
| } | |
| function formatChartDate(date) { | |
| return date.toLocaleDateString('en-US', { | |
| month: 'short', | |
| day: 'numeric' | |
| }); | |
| } | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=fullstackufo/roger-tracker" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |