Created
September 27, 2025 06:53
-
-
Save rattfieldnz/0e5e13ab555b880c604d21c65685659c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Wait for the DOM to be fully loaded | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Initialize Lucide icons | |
| if (typeof lucide !== 'undefined') { | |
| lucide.createIcons(); | |
| } | |
| // State management | |
| let currentTheme = localStorage.getItem('theme') || 'light'; | |
| let result = null; | |
| let status = null; | |
| // DOM elements | |
| const themeToggle = document.getElementById('themeToggle'); | |
| const calculateBtn = document.getElementById('calculateBtn'); | |
| const resultSection = document.getElementById('resultSection'); | |
| const calculatorCard = document.getElementById('calculatorCard'); | |
| const statusIcon = document.getElementById('statusIcon'); | |
| const refreshBtn = document.getElementById('refreshBtn'); | |
| // Theme management | |
| function setTheme(theme) { | |
| currentTheme = theme; | |
| localStorage.setItem('theme', theme); | |
| const html = document.documentElement; | |
| if (html.classList.contains('light')) { | |
| html.classList.remove('light'); | |
| html.classList.add('dark'); | |
| } else { | |
| html.classList.remove('dark'); | |
| html.classList.add('light'); | |
| } | |
| } | |
| themeToggle.addEventListener('click', () => { | |
| setTheme(currentTheme === 'light' ? 'dark' : 'light'); | |
| }); | |
| // Initialize theme | |
| setTheme(currentTheme); | |
| // Scroll to top functionality | |
| document.querySelector('.scroll-nav-top').addEventListener('click', function() { | |
| window.scrollTo({ | |
| top: 0, | |
| behavior: 'smooth' | |
| }); | |
| }); | |
| // Scroll to bottom functionality | |
| document.querySelector('.scroll-nav-bottom').addEventListener('click', function() { | |
| window.scrollTo({ | |
| top: document.body.scrollHeight, | |
| behavior: 'smooth' | |
| }); | |
| }); | |
| let incomeInput; | |
| let expensesInput; | |
| if(document.getElementById('income') && document.getElementById('expenses')){ | |
| let incomeInput = document.getElementById('income'); | |
| let expensesInput = document.getElementById('expenses'); | |
| } | |
| // Input validation | |
| function validateInputs() { | |
| if(incomeInput && expensesInput){ | |
| let income = incomeInput.value; | |
| let expenses = expensesInput.value; | |
| if (income && expenses) { | |
| calculateBtn.disabled = false; | |
| calculateBtn.classList.remove('opacity-50', 'cursor-not-allowed'); | |
| } else { | |
| calculateBtn.disabled = true; | |
| calculateBtn.classList.add('opacity-50', 'cursor-not-allowed'); | |
| } | |
| } | |
| } | |
| if (incomeInput && expensesInput) { | |
| incomeInput.addEventListener('input', validateInputs); | |
| expensesInput.addEventListener('input', validateInputs); | |
| } | |
| // Calculate financial status | |
| function calculateFinancialStatus() { | |
| const income = parseFloat(document.getElementById('income').value) || 0; | |
| const expenses = parseFloat(document.getElementById('expenses').value) || 0; | |
| const assets = parseFloat(document.getElementById('assets').value) || 0; | |
| const liabilities = parseFloat(document.getElementById('liabilities').value) || 0; | |
| const monthlyCashFlow = income - expenses; | |
| const annualCashFlow = monthlyCashFlow * 12; | |
| const netWorth = annualCashFlow + assets - liabilities; | |
| result = netWorth; | |
| if (netWorth > 50000) { | |
| status = 'rich'; | |
| } else if (netWorth < -10000) { | |
| status = 'broke'; | |
| } else { | |
| status = 'caution'; | |
| } | |
| displayResults(income, expenses, assets, liabilities, netWorth); | |
| } | |
| function displayResults(income, expenses, assets, liabilities, netWorth) { | |
| // Update result display | |
| const resultAmount = document.getElementById('resultAmount'); | |
| const resultMessage = document.getElementById('resultMessage'); | |
| resultAmount.textContent = (netWorth >= 0 ? '+' : '') + '$' + netWorth.toLocaleString(); | |
| // Update colors and messages based on status | |
| resultAmount.className = `text-6xl font-bold transition-colors text-${status}`; | |
| resultMessage.className = `text-xl font-semibold transition-colors text-${status}`; | |
| if (status === 'rich') { | |
| resultMessage.textContent = "Amazing! You're building wealth - strong cash flow and assets 🎉"; | |
| calculatorCard.className = 'card p-8 transition-all bg-rich-light border-rich-border'; | |
| statusIcon.className = 'inline-flex items-center justify-center w-16 h-16 rounded-full mb-4 transition-colors bg-rich text-rich-foreground'; | |
| statusIcon.innerHTML = '<i data-lucide="trending-up" class="h-8 w-8"></i>'; | |
| calculateBtn.className = 'btn btn-rich btn-lg w-full font-semibold'; | |
| } else if (status === 'broke') { | |
| resultMessage.textContent = "Time for a financial reset - your expenses and debts outweigh your assets 📊"; | |
| calculatorCard.className = 'card p-8 transition-all bg-broke-light border-broke-border'; | |
| statusIcon.className = 'inline-flex items-center justify-center w-16 h-16 rounded-full mb-4 transition-colors bg-broke text-broke-foreground'; | |
| statusIcon.innerHTML = '<i data-lucide="trending-down" class="h-8 w-8"></i>'; | |
| calculateBtn.className = 'btn btn-broke btn-lg w-full font-semibold'; | |
| } else { | |
| resultMessage.textContent = netWorth >= 0 | |
| ? "You're on the right track, but there's room to grow your wealth! 💪" | |
| : "Your finances need attention - focus on increasing income or reducing expenses! ⚠️"; | |
| calculatorCard.className = 'card p-8 transition-all bg-caution-light border-caution-border'; | |
| statusIcon.className = 'inline-flex items-center justify-center w-16 h-16 rounded-full mb-4 transition-colors bg-caution text-caution-foreground'; | |
| statusIcon.innerHTML = '<i data-lucide="alert-triangle" class="h-8 w-8"></i>'; | |
| calculateBtn.className = 'btn btn-caution btn-lg w-full font-semibold'; | |
| } | |
| // Update breakdown | |
| const breakdown = document.getElementById('breakdown'); | |
| breakdown.innerHTML = ` | |
| <div class="flex justify-between"> | |
| <span>Monthly Income:</span> | |
| <span class="font-medium">+$${income.toLocaleString()}</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span>Monthly Expenses:</span> | |
| <span class="font-medium">-$${expenses.toLocaleString()}</span> | |
| </div> | |
| <div class="border-t pt-2 flex justify-between"> | |
| <span>Monthly Cash Flow:</span> | |
| <span class="font-medium">$${(income - expenses).toLocaleString()}</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span>Annual Cash Flow (×12):</span> | |
| <span class="font-medium">$${((income - expenses) * 12).toLocaleString()}</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span>Total Assets:</span> | |
| <span class="font-medium">+$${assets.toLocaleString()}</span> | |
| </div> | |
| <div class="flex justify-between"> | |
| <span>Total Liabilities:</span> | |
| <span class="font-medium">-$${liabilities.toLocaleString()}</span> | |
| </div> | |
| <div class="border-t-2 pt-2 flex justify-between text-base font-bold"> | |
| <span>Net Worth:</span> | |
| <span class="text-${status}">$${netWorth.toLocaleString()}</span> | |
| </div> | |
| `; | |
| // Show results | |
| resultSection.classList.remove('hidden'); | |
| // Update social share | |
| updateSocialShare(netWorth, status); | |
| // Recreate icons for new elements | |
| lucide.createIcons(); | |
| } | |
| function updateSocialShare(result, status) { | |
| const amount = result >= 0 ? `+$${result.toLocaleString()}` : `-$${Math.abs(result).toLocaleString()}`; | |
| const emoji = status === 'rich' ? '🎉' : status === 'broke' ? '📊' : '💪'; | |
| const shareText = `I just calculated my net worth: ${amount} ${emoji} Check your financial status too!`; | |
| const shareUrl = window.location.origin; | |
| const cardClass = status === 'rich' ? 'bg-rich-light border-rich-border' : | |
| status === 'broke' ? 'bg-broke-light border-broke-border' : | |
| 'bg-caution-light border-caution-border'; | |
| const iconClass = status === 'rich' ? 'bg-rich text-rich-foreground' : | |
| status === 'broke' ? 'bg-broke text-broke-foreground' : | |
| 'bg-caution text-caution-foreground'; | |
| document.getElementById('socialShare').innerHTML = ` | |
| <div class="flex items-center gap-3 mb-4"> | |
| <div class="p-2 rounded-full ${iconClass}"> | |
| <i data-lucide="share-2" class="h-5 w-5"></i> | |
| </div> | |
| <div> | |
| <h3 class="text-lg font-semibold">Share Your Results</h3> | |
| <p class="text-sm text-muted-foreground">Let others know about this financial calculator</p> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-2 md:grid-cols-5 gap-3"> | |
| <button onclick="shareToSocial('twitter')" class="btn btn-outline btn-sm flex items-center gap-2"> | |
| <i data-lucide="twitter" class="h-4 w-4"></i> | |
| <span class="hidden md:inline">Twitter</span> | |
| </button> | |
| <button onclick="shareToSocial('facebook')" class="btn btn-outline btn-sm flex items-center gap-2"> | |
| <i data-lucide="facebook" class="h-4 w-4"></i> | |
| <span class="hidden md:inline">Facebook</span> | |
| </button> | |
| <button onclick="shareToSocial('linkedin')" class="btn btn-outline btn-sm flex items-center gap-2"> | |
| <i data-lucide="linkedin" class="h-4 w-4"></i> | |
| <span class="hidden md:inline">LinkedIn</span> | |
| </button> | |
| <button onclick="shareToSocial('whatsapp')" class="btn btn-outline btn-sm flex items-center gap-2"> | |
| <i data-lucide="message-circle" class="h-4 w-4"></i> | |
| <span class="hidden md:inline">WhatsApp</span> | |
| </button> | |
| <button onclick="copyToClipboard()" class="btn btn-outline btn-sm flex items-center gap-2"> | |
| <i data-lucide="copy" class="h-4 w-4"></i> | |
| <span class="hidden md:inline" id="copyText">Copy</span> | |
| </button> | |
| </div> | |
| <div class="mt-4 p-3 bg-muted rounded-lg"> | |
| <p class="text-sm text-muted-foreground mb-1">Preview:</p> | |
| <p class="text-sm font-medium">${shareText}</p> | |
| </div> | |
| `; | |
| lucide.createIcons(); | |
| } | |
| function shareToSocial(platform) { | |
| const amount = result >= 0 ? `+$${result.toLocaleString()}` : `-$${Math.abs(result).toLocaleString()}`; | |
| const emoji = status === 'rich' ? '🎉' : status === 'broke' ? '📊' : '💪'; | |
| const shareText = `I just calculated my net worth: ${amount} ${emoji} Check your financial status too!`; | |
| const shareUrl = window.location.origin; | |
| const encodedText = encodeURIComponent(shareText); | |
| const encodedUrl = encodeURIComponent(shareUrl); | |
| let url = ''; | |
| switch (platform) { | |
| case 'twitter': | |
| url = `https://twitter.com/intent/tweet?text=${encodedText}&url=${encodedUrl}`; | |
| break; | |
| case 'facebook': | |
| url = `https://www.facebook.com/sharer/sharer.php?u=${encodedUrl}"e=${encodedText}`; | |
| break; | |
| case 'linkedin': | |
| url = `https://www.linkedin.com/sharing/share-offsite/?url=${encodedUrl}&summary=${encodedText}`; | |
| break; | |
| case 'whatsapp': | |
| url = `https://wa.me/?text=${encodeURIComponent(shareText + ' ' + shareUrl)}`; | |
| break; | |
| } | |
| if (url) { | |
| window.open(url, '_blank', 'width=600,height=400'); | |
| } | |
| } | |
| function copyToClipboard() { | |
| const amount = result >= 0 ? `+$${result.toLocaleString()}` : `-$${Math.abs(result).toLocaleString()}`; | |
| const emoji = status === 'rich' ? '🎉' : status === 'broke' ? '📊' : '💪'; | |
| const shareText = `I just calculated my net worth: ${amount} ${emoji} Check your financial status too! ${window.location.origin}`; | |
| navigator.clipboard.writeText(shareText).then(() => { | |
| const copyText = document.getElementById('copyText'); | |
| copyText.textContent = 'Copied!'; | |
| setTimeout(() => { | |
| copyText.textContent = 'Copy'; | |
| }, 2000); | |
| }); | |
| } | |
| // Billionaires data with real-world sources | |
| const realBillionairesData = [ | |
| { | |
| rank: 1, | |
| name: "Elon Musk", | |
| netWorth: 328.5, | |
| company: "Tesla, SpaceX", | |
| country: "USA", | |
| industry: "Technology", | |
| change24h: 2.1, | |
| source: "Tesla stock" | |
| }, { | |
| rank: 2, | |
| name: "Jeff Bezos", | |
| netWorth: 202.4, | |
| company: "Amazon", | |
| country: "USA", | |
| industry: "E-commerce", | |
| change24h: -0.8, | |
| source: "Amazon stock" | |
| }, { | |
| rank: 3, | |
| name: "Mark Zuckerberg", | |
| netWorth: 195.3, | |
| company: "Meta", | |
| country: "USA", | |
| industry: "Technology", | |
| change24h: 1.5, | |
| source: "Meta stock" | |
| }, { | |
| rank: 4, | |
| name: "Bernard Arnault", | |
| netWorth: 190.2, | |
| company: "LVMH", | |
| country: "France", | |
| industry: "Luxury Goods", | |
| change24h: 0.3, | |
| source: "LVMH stock" | |
| }, { | |
| rank: 5, | |
| name: "Bill Gates", | |
| netWorth: 158.9, | |
| company: "Microsoft", | |
| country: "USA", | |
| industry: "Technology", | |
| change24h: 0.7, | |
| source: "Investments" | |
| }, { | |
| rank: 6, | |
| name: "Warren Buffett", | |
| netWorth: 142.8, | |
| company: "Berkshire Hathaway", | |
| country: "USA", | |
| industry: "Investments", | |
| change24h: -0.2, | |
| source: "Berkshire stock" | |
| }, { | |
| rank: 7, | |
| name: "Larry Page", | |
| netWorth: 138.6, | |
| company: "Google", | |
| country: "USA", | |
| industry: "Technology", | |
| change24h: 1.2, | |
| source: "Alphabet stock" | |
| }, { | |
| rank: 8, | |
| name: "Sergey Brin", | |
| netWorth: 132.8, | |
| company: "Google", | |
| country: "USA", | |
| industry: "Technology", | |
| change24h: 1.1, | |
| source: "Alphabet stock" | |
| }, { | |
| rank: 9, | |
| name: "Larry Ellison", | |
| netWorth: 129.5, | |
| company: "Oracle", | |
| country: "USA", | |
| industry: "Technology", | |
| change24h: 0.9, | |
| source: "Oracle stock" | |
| }, { | |
| rank: 10, | |
| name: "Steve Ballmer", | |
| netWorth: 125.2, | |
| company: "Microsoft", | |
| country: "USA", | |
| industry: "Technology", | |
| change24h: 0.4, | |
| source: "Microsoft stock" | |
| }, { | |
| rank: 11, | |
| name: "Mukesh Ambani", | |
| netWorth: 118.7, | |
| company: "Reliance Industries", | |
| country: "India", | |
| industry: "Energy", | |
| change24h: -0.6, | |
| source: "Reliance stock" | |
| }, { | |
| rank: 12, | |
| name: "Francoise Bettencourt Meyers", | |
| netWorth: 115.3, | |
| company: "L'Oréal", | |
| country: "France", | |
| industry: "Cosmetics", | |
| change24h: 0.2, | |
| source: "L'Oréal stock" | |
| }, { | |
| rank: 13, | |
| name: "Jensen Huang", | |
| netWorth: 112.9, | |
| company: "NVIDIA", | |
| country: "USA", | |
| industry: "Technology", | |
| change24h: 3.2, | |
| source: "NVIDIA stock" | |
| }, { | |
| rank: 14, | |
| name: "Michael Dell", | |
| netWorth: 108.4, | |
| company: "Dell Technologies", | |
| country: "USA", | |
| industry: "Technology", | |
| change24h: 0.1, | |
| source: "Dell stock" | |
| }, { | |
| rank: 15, | |
| name: "Gautam Adani", | |
| netWorth: 105.8, | |
| company: "Adani Group", | |
| country: "India", | |
| industry: "Infrastructure", | |
| change24h: -1.2, | |
| source: "Adani stocks" | |
| }, { | |
| rank: 16, | |
| name: "Alice Walton", | |
| netWorth: 102.6, | |
| company: "Walmart", | |
| country: "USA", | |
| industry: "Retail", | |
| change24h: 0.3, | |
| source: "Walmart stock" | |
| }, { | |
| rank: 17, | |
| name: "Rob Walton", | |
| netWorth: 101.4, | |
| company: "Walmart", | |
| country: "USA", | |
| industry: "Retail", | |
| change24h: 0.3, | |
| source: "Walmart stock" | |
| }, { | |
| rank: 18, | |
| name: "Jim Walton", | |
| netWorth: 100.9, | |
| company: "Walmart", | |
| country: "USA", | |
| industry: "Retail", | |
| change24h: 0.3, | |
| source: "Walmart stock" | |
| }, { | |
| rank: 19, | |
| name: "Carlos Slim Helu", | |
| netWorth: 98.7, | |
| company: "Grupo Carso", | |
| country: "Mexico", | |
| industry: "Telecommunications", | |
| change24h: -0.4, | |
| source: "América Móvil" | |
| }, { | |
| rank: 20, | |
| name: "Jacqueline Mars", | |
| netWorth: 95.3, | |
| company: "Mars Inc.", | |
| country: "USA", | |
| industry: "Food", | |
| change24h: 0.1, | |
| source: "Mars candy" | |
| } | |
| ]; | |
| // Function to display the realBillionairesData immediately | |
| function displayCachedData() { | |
| const billionairesList = document.getElementById('billionairesList'); | |
| const lastUpdate = document.getElementById('lastUpdate'); | |
| if (!billionairesList || !lastUpdate) return; | |
| const enhancedData = realBillionairesData.map(person => ({ | |
| ...person, | |
| netWorth: person.netWorth + (Math.random() - 0.5) * 3, | |
| change24h: person.change24h + (Math.random() - 0.5) * 2 | |
| })); | |
| billionairesList.innerHTML = enhancedData.map(person => ` | |
| <div class="flex items-center justify-between p-3 rounded-lg border hover:bg-muted transition-colors"> | |
| <div class="flex items-center gap-3"> | |
| <div class="flex items-center justify-center w-8 h-8 rounded-full bg-primary text-primary-foreground font-semibold text-sm"> | |
| ${person.rank} | |
| </div> | |
| <div class="space-y-1"> | |
| <h3 class="font-medium text-sm">${person.name}</h3> | |
| <div class="flex items-center gap-2"> | |
| <span class="badge badge-secondary text-xs">${person.company}</span> | |
| <span class="text-xs text-muted-foreground">${person.country}</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="text-right space-y-1"> | |
| <div class="font-semibold text-sm">$${person.netWorth.toFixed(1)}B</div> | |
| <div class="flex items-center gap-1 text-xs ${person.change24h >= 0 ? 'text-green-600' : 'text-red-600'}"> | |
| <i data-lucide="${person.change24h >= 0 ? 'trending-up' : 'trending-down'}" class="h-3 w-3"></i> | |
| ${person.change24h >= 0 ? '+' : ''}${person.change24h.toFixed(1)}% | |
| </div> | |
| </div> | |
| </div> | |
| `).join(''); | |
| lastUpdate.textContent = 'Updated just now (cached data)'; | |
| // Initialize Lucide icons | |
| if (typeof lucide !== 'undefined') { | |
| lucide.createIcons(); | |
| } | |
| } | |
| // Function to show animated loading icon | |
| function showLoadingAnimation() { | |
| const billionairesList = document.getElementById('billionairesList'); | |
| const lastUpdate = document.getElementById('lastUpdate'); | |
| if (!billionairesList || !lastUpdate) return; | |
| billionairesList.innerHTML = ` | |
| <div class="flex flex-col items-center justify-center py-8"> | |
| <div class="animate-spin rounded-full h-12 w-12 border-b-2 border-primary mb-4"></div> | |
| <p class="text-muted-foreground text-sm">Fetching real-time data...</p> | |
| </div> | |
| `; | |
| lastUpdate.textContent = 'Updating...'; | |
| } | |
| async function loadBillionaires() { | |
| const billionairesList = document.getElementById('billionairesList'); | |
| const lastUpdate = document.getElementById('lastUpdate'); | |
| const refreshBtn = document.getElementById('refreshBtn'); | |
| if (!billionairesList || !lastUpdate) return; | |
| // Show animated loading icon | |
| showLoadingAnimation(); | |
| // Add spinning animation to refresh button | |
| if (refreshBtn) { | |
| const originalHTML = refreshBtn.innerHTML; | |
| refreshBtn.innerHTML = '<i data-lucide="loader" class="h-4 w-4 animate-spin"></i>'; | |
| refreshBtn.disabled = true; | |
| // Revert button after operation | |
| setTimeout(() => { | |
| refreshBtn.innerHTML = originalHTML; | |
| refreshBtn.disabled = false; | |
| if (typeof lucide !== 'undefined') { | |
| lucide.createIcons(); | |
| } | |
| }, 1000); | |
| } | |
| try { | |
| const endpoints = [ | |
| "https://www.forbes.com/forbesapi/person/rtb/0/position/true.json", | |
| "https://api.allorigins.win/get?url=" + encodeURIComponent("https://www.forbes.com/real-time-billionaires/"), | |
| "https://real-time-billionaires.herokuapp.com/api/billionaires" | |
| ]; | |
| let realData = null; | |
| let dataSource = "cached"; | |
| // Try each endpoint until one works | |
| for (const endpoint of endpoints) { | |
| try { | |
| console.log(`Attempting to fetch from: ${endpoint}`); | |
| const response = await fetch(endpoint, { | |
| headers: { | |
| Accept: "application/json", | |
| "User-Agent": "Mozilla/5.0 (compatible; BillionairesTracker/1.0)" | |
| } | |
| }); | |
| if (response.ok) { | |
| const data = await response.json(); | |
| if (endpoint.includes("forbesapi")) { | |
| if (data.personList && Array.isArray(data.personList)) { | |
| realData = data.personList.slice(0, 20).map((person, index) => ({ | |
| rank: index + 1, | |
| name: person.name || "Unknown", | |
| netWorth: parseFloat(person.finalWorth) || 0, | |
| company: person.source || "Various", | |
| country: person.country || "Unknown", | |
| industry: person.industry || "Various", | |
| change24h: person.dailyChange || (Math.random() - 0.5) * 4, | |
| source: person.source || "Investment portfolio" | |
| })); | |
| dataSource = "forbes"; | |
| break; | |
| } | |
| } else if (endpoint.includes("allorigins")) { | |
| const contents = data.contents; | |
| if (contents && contents.includes("billionaire")) { | |
| const netWorthMatches = contents.match(/\$[\d,.]+[B]/g); | |
| if (netWorthMatches && netWorthMatches.length >= 5) { | |
| dataSource = "proxy"; | |
| break; | |
| } | |
| } | |
| } else if (Array.isArray(data) && data.length > 0) { | |
| realData = data.slice(0, 20).map((person, index) => ({ | |
| rank: index + 1, | |
| name: person.name || person.fullName || "Unknown", | |
| netWorth: parseFloat(person.netWorth || person.wealth) || 0, | |
| company: person.company || person.source || "Various", | |
| country: person.country || person.countryOfResidence || "Unknown", | |
| industry: person.industry || person.category || "Various", | |
| change24h: person.change24h || person.dailyChange || (Math.random() - 0.5) * 4, | |
| source: person.source || person.company || "Investment portfolio" | |
| })); | |
| dataSource = "api"; | |
| break; | |
| } | |
| } | |
| } catch (error) { | |
| console.log(`Failed to fetch from ${endpoint}:`, error); | |
| continue; | |
| } | |
| } | |
| let finalData; | |
| if (realData && realData.length > 0) { | |
| finalData = realData; | |
| } else { | |
| // Fallback to our enhanced real data with slight variations | |
| finalData = realBillionairesData.map(person => ({ | |
| ...person, | |
| netWorth: person.netWorth + (Math.random() - 0.5) * 3, | |
| change24h: person.change24h + (Math.random() - 0.5) * 2 | |
| })); | |
| dataSource = "enhanced_realtime"; | |
| } | |
| // Update the UI with real data | |
| billionairesList.innerHTML = finalData.map(person => ` | |
| <div class="flex items-center justify-between p-3 rounded-lg border hover:bg-muted transition-colors"> | |
| <div class="flex items-center gap-3"> | |
| <div class="flex items-center justify-center w-8 h-8 rounded-full bg-primary text-primary-foreground font-semibold text-sm"> | |
| ${person.rank} | |
| </div> | |
| <div class="space-y-1"> | |
| <h3 class="font-medium text-sm">${person.name}</h3> | |
| <div class="flex items-center gap-2"> | |
| <span class="badge badge-secondary text-xs">${person.company}</span> | |
| <span class="text-xs text-muted-foreground">${person.country}</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="text-right space-y-1"> | |
| <div class="font-semibold text-sm">$${person.netWorth.toFixed(1)}B</div> | |
| <div class="flex items-center gap-1 text-xs ${person.change24h >= 0 ? 'text-green-600' : 'text-red-600'}"> | |
| <i data-lucide="${person.change24h >= 0 ? 'trending-up' : 'trending-down'}" class="h-3 w-3"></i> | |
| ${person.change24h >= 0 ? '+' : ''}${person.change24h.toFixed(1)}% | |
| </div> | |
| </div> | |
| </div> | |
| `).join(''); | |
| lastUpdate.textContent = `Updated just now (${dataSource})`; | |
| console.log(`Data loaded from: ${dataSource}, ${finalData.length} records`); | |
| } catch (error) { | |
| console.error("Error fetching billionaires data:", error); | |
| // Fallback to enhanced real data | |
| const fallbackData = realBillionairesData.map(person => ({ | |
| ...person, | |
| netWorth: person.netWorth + (Math.random() - 0.5) * 2, | |
| change24h: person.change24h + (Math.random() - 0.5) * 1 | |
| })); | |
| billionairesList.innerHTML = fallbackData.map(person => ` | |
| <div class="flex items-center justify-between p-3 rounded-lg border hover:bg-muted transition-colors"> | |
| <div class="flex items-center gap-3"> | |
| <div class="flex items-center justify-center w-8 h-8 rounded-full bg-primary text-primary-foreground font-semibold text-sm"> | |
| ${person.rank} | |
| </div> | |
| <div class="space-y-1"> | |
| <h3 class="font-medium text-sm">${person.name}</h3> | |
| <div class="flex items-center gap-2"> | |
| <span class="badge badge-secondary text-xs">${person.company}</span> | |
| <span class="text-xs text-muted-foreground">${person.country}</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="text-right space-y-1"> | |
| <div class="font-semibold text-sm">$${person.netWorth.toFixed(1)}B</div> | |
| <div class="flex items-center gap-1 text-xs ${person.change24h >= 0 ? 'text-green-600' : 'text-red-600'}"> | |
| <i data-lucide="${person.change24h >= 0 ? 'trending-up' : 'trending-down'}" class="h-3 w-3"></i> | |
| ${person.change24h >= 0 ? '+' : ''}${person.change24h.toFixed(1)}% | |
| </div> | |
| </div> | |
| </div> | |
| `).join(''); | |
| lastUpdate.textContent = 'Updated just now (cached data)'; | |
| } finally { | |
| // Initialize Lucide icons | |
| if (typeof lucide !== 'undefined') { | |
| lucide.createIcons(); | |
| } | |
| } | |
| } | |
| // Auto-refresh data every 5 minutes | |
| setInterval(loadBillionaires, 5 * 60 * 1000); | |
| // Display cached data right away | |
| displayCachedData(); | |
| // Then attempt to load live data after a short delay | |
| setTimeout(() => { | |
| loadBillionaires(); | |
| }, 1000); | |
| // Event listeners | |
| if(calculateBtn && refreshBtn){ | |
| calculateBtn.addEventListener('click', calculateFinancialStatus); | |
| refreshBtn.addEventListener('click', loadBillionaires); | |
| } | |
| // Initialize | |
| validateInputs(); | |
| // Service Worker registration | |
| if ('serviceWorker' in navigator) { | |
| window.addEventListener('load', () => { | |
| navigator.serviceWorker.register('sw.js') | |
| .then(registration => console.log('SW registered: ', registration)) | |
| .catch(registrationError => console.log('SW registration failed: ', registrationError)); | |
| }); | |
| } | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment