Spaces:
Running
Running
| /* ================================================================ | |
| FinWise β Shared JavaScript | |
| Utilities, navigation, localStorage helpers | |
| ================================================================ */ | |
| // ββ Page detection ββββββββββββββββββββββββββββββββββββββββββββββββ | |
| (function setActiveNav() { | |
| const page = location.pathname.split('/').pop() || 'index.html'; | |
| document.querySelectorAll('.nav-item, .bottom-nav-item').forEach(el => { | |
| const href = el.getAttribute('href') || ''; | |
| if (href === page || (page === '' && href === 'index.html')) { | |
| el.classList.add('active'); | |
| } | |
| }); | |
| })(); | |
| // ββ LocalStorage Helpers ββββββββββββββββββββββββββββββββββββββββββ | |
| const LS = { | |
| get(key, fallback = null) { | |
| try { const v = localStorage.getItem(key); return v ? JSON.parse(v) : fallback; } | |
| catch { return fallback; } | |
| }, | |
| set(key, val) { | |
| try { localStorage.setItem(key, JSON.stringify(val)); return true; } | |
| catch { return false; } | |
| } | |
| }; | |
| // ββ Default Portfolio Data ββββββββββββββββββββββββββββββββββββββββ | |
| const DEFAULT_PORTFOLIO = { | |
| assets: [ | |
| { ticker: 'VOO', name: 'Vanguard S&P 500 ETF', pct: 30, price: 478.22, shares: 3.1, color: '#22d3ee', type: 'ETF' }, | |
| { ticker: 'QQQ', name: 'Invesco Nasdaq 100 ETF', pct: 20, price: 456.80, shares: 2.2, color: '#8b5cf6', type: 'ETF' }, | |
| { ticker: 'NVDA', name: 'NVIDIA Corporation', pct: 15, price: 875.40, shares: 0.85, color: '#10b981', type: 'Stock' }, | |
| { ticker: 'AAPL', name: 'Apple Inc.', pct: 12, price: 188.60, shares: 3.2, color: '#f59e0b', type: 'Stock' }, | |
| { ticker: 'BND', name: 'Vanguard Bond ETF', pct: 13, price: 73.40, shares: 8.8, color: '#6366f1', type: 'Bond' }, | |
| { ticker: 'GLD', name: 'SPDR Gold Trust', pct: 7, price: 218.10, shares: 1.6, color: '#f43f5e', type: 'Commodity' }, | |
| { ticker: 'AMZN', name: 'Amazon.com Inc.', pct: 3, price: 188.90, shares: 0.8, color: '#0ea5e9', type: 'Stock' }, | |
| ], | |
| totalInvested: 12500, | |
| riskProfile: 'Moderate', | |
| goals: ['Wealth Building'], | |
| lastUpdated: new Date().toISOString() | |
| }; | |
| function getPortfolio() { | |
| return LS.get('fw_portfolio', DEFAULT_PORTFOLIO); | |
| } | |
| function savePortfolio(p) { | |
| p.lastUpdated = new Date().toISOString(); | |
| LS.set('fw_portfolio', p); | |
| } | |
| // ββ Simulated Market Data βββββββββββββββββββββββββββββββββββββββββ | |
| const MARKET_PRICES = { | |
| 'VOO': { price: 478.22, change: +1.24, changePct: +0.26 }, | |
| 'QQQ': { price: 456.80, change: -2.10, changePct: -0.46 }, | |
| 'NVDA': { price: 875.40, change: +18.5, changePct: +2.16 }, | |
| 'AAPL': { price: 188.60, change: +0.80, changePct: +0.43 }, | |
| 'BND': { price: 73.40, change: -0.05, changePct: -0.07 }, | |
| 'GLD': { price: 218.10, change: +3.20, changePct: +1.49 }, | |
| 'AMZN': { price: 188.90, change: +1.60, changePct: +0.86 }, | |
| 'VTI': { price: 240.30, change: +0.94, changePct: +0.39 }, | |
| 'TSLA': { price: 182.30, change: -4.20, changePct: -2.25 }, | |
| 'WMT': { price: 67.80, change: +0.30, changePct: +0.44 }, | |
| 'MCD': { price: 281.50, change: +1.10, changePct: +0.39 }, | |
| }; | |
| // ββ Historical Performance Generator βββββββββββββββββββββββββββββ | |
| function generateHistory(days = 180, startVal = 10000, volatility = 0.012) { | |
| const data = []; | |
| let val = startVal; | |
| const now = Date.now(); | |
| for (let i = days; i >= 0; i--) { | |
| const date = new Date(now - i * 86400000); | |
| const change = (Math.random() - 0.46) * volatility; | |
| val = val * (1 + change); | |
| data.push({ date: date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }), value: Math.round(val * 100) / 100 }); | |
| } | |
| return data; | |
| } | |
| // ββ Number Formatting βββββββββββββββββββββββββββββββββββββββββββββ | |
| function fmt$(n) { return '$' + n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } | |
| function fmtPct(n) { return (n > 0 ? '+' : '') + n.toFixed(2) + '%'; } | |
| function fmtK(n) { return n >= 1000000 ? '$' + (n/1000000).toFixed(2) + 'M' : n >= 1000 ? '$' + (n/1000).toFixed(1) + 'K' : '$' + n.toFixed(0); } | |
| // ββ Animated Counter ββββββββββββββββββββββββββββββββββββββββββββββ | |
| function animateCounter(el, target, prefix = '', suffix = '', duration = 1200) { | |
| const start = parseFloat(el.textContent.replace(/[^0-9.-]/g, '')) || 0; | |
| const startTime = performance.now(); | |
| function step(now) { | |
| const p = Math.min((now - startTime) / duration, 1); | |
| const ease = 1 - Math.pow(1 - p, 3); | |
| const val = start + (target - start) * ease; | |
| el.textContent = prefix + val.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + suffix; | |
| if (p < 1) requestAnimationFrame(step); | |
| } | |
| requestAnimationFrame(step); | |
| } | |
| // ββ Chart.js Defaults βββββββββββββββββββββββββββββββββββββββββββββ | |
| function applyChartDefaults() { | |
| if (typeof Chart === 'undefined') return; | |
| Chart.defaults.color = '#8faac8'; | |
| Chart.defaults.borderColor = 'rgba(34,211,238,0.10)'; | |
| Chart.defaults.font.family = "'DM Sans', sans-serif"; | |
| } | |
| // ββ Ticker Data for Sidebar βββββββββββββββββββββββββββββββββββββββ | |
| const TICKERS = [ | |
| { sym: 'S&P 500', val: '5,308', chg: '+0.26%', up: true }, | |
| { sym: 'NASDAQ', val: '16,742', chg: '-0.46%', up: false }, | |
| { sym: 'BTC', val: '68,420', chg: '+2.14%', up: true }, | |
| { sym: 'GOLD', val: '2,318', chg: '+1.49%', up: true }, | |
| ]; | |
| function renderSidebarTickers() { | |
| const container = document.getElementById('sidebar-tickers'); | |
| if (!container) return; | |
| container.innerHTML = TICKERS.map(t => ` | |
| <div class="ticker-item"> | |
| <span class="ticker-name">${t.sym}</span> | |
| <span class="${t.up ? 'up' : 'down'}">${t.chg}</span> | |
| </div> | |
| `).join(''); | |
| } | |
| // ββ Risk Score Calculator βββββββββββββββββββββββββββββββββββββββββ | |
| function calcRiskScore(portfolio) { | |
| const weights = { ETF: 3, Stock: 5, Bond: 1, Commodity: 4 }; | |
| let score = 0; | |
| portfolio.assets.forEach(a => { | |
| score += (weights[a.type] || 3) * (a.pct / 100); | |
| }); | |
| return Math.round((score / 5) * 100); // 0-100 | |
| } | |
| // ββ Diversification Score βββββββββββββββββββββββββββββββββββββββββ | |
| function calcDiversification(portfolio) { | |
| const types = [...new Set(portfolio.assets.map(a => a.type))].length; | |
| const count = portfolio.assets.length; | |
| const maxPct = Math.max(...portfolio.assets.map(a => a.pct)); | |
| const concentration = maxPct > 40 ? 0.6 : maxPct > 25 ? 0.8 : 1.0; | |
| return Math.round(((types / 4) * 0.4 + (Math.min(count, 7) / 7) * 0.4 + concentration * 0.2) * 100); | |
| } | |
| // ββ Color Palette βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const ASSET_COLORS = ['#22d3ee','#10b981','#8b5cf6','#f59e0b','#f43f5e','#6366f1','#0ea5e9','#34d399','#a78bfa','#fb923c']; | |
| // ββ Toast Notification ββββββββββββββββββββββββββββββββββββββββββββ | |
| function showToast(msg, type = 'success') { | |
| const t = document.createElement('div'); | |
| t.style.cssText = ` | |
| position:fixed; bottom:90px; right:20px; z-index:9999; | |
| padding:12px 20px; border-radius:10px; font-size:13px; font-weight:600; | |
| background:${type === 'success' ? 'rgba(16,185,129,0.95)' : 'rgba(244,63,94,0.95)'}; | |
| color:#fff; box-shadow:0 8px 32px rgba(0,0,0,0.4); | |
| animation: fadeInUp 0.3s ease; | |
| max-width: 300px; | |
| `; | |
| t.textContent = msg; | |
| document.body.appendChild(t); | |
| setTimeout(() => t.remove(), 3000); | |
| } | |
| // ββ On DOM Ready ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| document.addEventListener('DOMContentLoaded', () => { | |
| applyChartDefaults(); | |
| renderSidebarTickers(); | |
| }); | |