// Global Variables let debounceTimer; // --- INITIALIZATION --- document.addEventListener('DOMContentLoaded', () => { // Expandable Cards to Modal Logic document.addEventListener('click', (e) => { const card = e.target.closest('.expandable-card'); const isExpandBtn = e.target.closest('.card-expand-btn'); if (card && (isExpandBtn || e.target.tagName === 'H3' || e.target.closest('.card-header-flex'))) { // Extract content const title = card.querySelector('h3').innerText; const bodyHtml = card.querySelector('.card-body').innerHTML; // Populate modal document.getElementById('modalTitle').innerText = title; document.getElementById('modalBody').innerHTML = bodyHtml; // Show modal document.getElementById('globalModal').classList.add('show'); } }); window.closeModal = function() { document.getElementById('globalModal').classList.remove('show'); }; window.toggleSidebar = function() { const sidebar = document.getElementById('appSidebar'); const mainContent = document.querySelector('.main-content'); if(sidebar) sidebar.classList.toggle('open'); if(mainContent) mainContent.classList.toggle('sidebar-open'); }; // Mouse Glow Tracking document.addEventListener("mousemove", (e) => { document.querySelectorAll(".mouse-glow, .glass-panel, .expandable-card").forEach((el) => { const rect = el.getBoundingClientRect(); el.style.setProperty("--mouse-x", `${e.clientX - rect.left}px`); el.style.setProperty("--mouse-y", `${e.clientY - rect.top}px`); el.classList.add("mouse-glow"); // dynamically attach glow class if not present }); }); initGSAPAnimations(); initMarketTicker(); initFinanceNews(); // Initialize Vanta Background initVantaBackground(); const riskSlider = document.getElementById('risk'); const riskVal = document.getElementById('riskVal'); if (riskSlider && riskVal) { riskSlider.addEventListener('input', (e) => { riskVal.textContent = e.target.value; // GSAP tactical feedback animation gsap.fromTo(riskVal, { scale: 1.5, color: '#3b82f6', textShadow: '0 0 20px #3b82f6' }, { scale: 1, color: '#f8fafc', textShadow: 'none', duration: 0.4, ease: "back.out(1.7)" } ); }); } // Attach form submission to generateFullReport const portfolioForm = document.getElementById('portfolioForm'); if (portfolioForm) { portfolioForm.addEventListener('submit', async (e) => { e.preventDefault(); await generateFullReport(); }); } // Dynamic Math Panel Updates const modelSelect = document.getElementById('model'); if (modelSelect) { modelSelect.addEventListener('change', (e) => { const mathFormula = document.getElementById('active-math-formula'); const mathDesc = document.getElementById('active-math-desc'); const val = e.target.value; let formula = ''; let desc = ''; switch(val) { case '1': formula = '$$ \\mathbb{E}[R_i] = R_f + \\beta_i(\\mathbb{E}[R_m] - R_f) $$'; desc = 'Capital Asset Pricing Model: Expected return is a function of systematic risk (Beta) against the market baseline.'; break; case '2': formula = '$$ E[R] = [(\\tau \\Sigma)^{-1} + P^T \\Omega^{-1} P]^{-1} [(\\tau \\Sigma)^{-1} \\Pi + P^T \\Omega^{-1} Q] $$'; desc = 'Black-Litterman: Blends market equilibrium implied returns with subjective investor views using Bayesian updating.'; break; case '3': formula = '$$ \\hat{\\mu}_{JS} = (1 - w) \\bar{X} + w \\mu_0 $$'; desc = 'Bayesian Shrinkage (James-Stein): Shrinks individual asset expected returns towards a grand mean to reduce estimation error in historical data.'; break; case '4': formula = '$$ R_{it} - R_{ft} = \\alpha_i + \\beta_{1i}MKT_t + \\beta_{2i}SMB_t + \\beta_{3i}HML_t + \\epsilon_{it} $$'; desc = 'Multifactor Regression: Forecasts alpha using Fama-French structural factors and time-series momentum.'; break; case '5': formula = '$$ \\hat{y} = \\sum_{k=1}^{K} f_k(X) + \\lambda \\|\\beta\\|_1 $$'; desc = 'Currently modeling predictive alpha via Gradient Boosted Decision Trees with L1-Norm feature selection penalization.'; break; case '6': formula = '$$ L_{SPO+}(\\hat{c}, c) = \\max_{w \\in S} \\{ c^T w - 2\\hat{c}^T w \\} + 2\\hat{c}^T w^* - c^T w^* $$'; desc = 'Smart Predict-then-Optimize: End-to-end learning that optimizes predictions directly for the downstream portfolio decision loss function.'; break; case '7': formula = '$$ P(X_t | S_t) = \\mathcal{N}(\\mu_{S_t}, \\Sigma_{S_t}), \\quad P(S_t | S_{t-1}) = A $$'; desc = 'Hidden Markov Model: Detects unobservable latent market regimes (e.g. Bull vs Bear) to dynamically switch alpha models.'; break; } if (mathFormula && mathDesc) { mathFormula.innerHTML = formula; mathDesc.innerHTML = desc; if (window.MathJax) { MathJax.typesetPromise([mathFormula]); } } }); } // Suite Tabs logic document.querySelectorAll('.suite-tab').forEach(tab => { tab.addEventListener('click', (e) => { document.querySelectorAll('.suite-tab').forEach(t => t.classList.remove('active')); e.target.classList.add('active'); // Currently all tabs just show the "View Comprehensive Report" button }); }); // (Duplicate form listener removed — already attached above) // Router History Listener window.addEventListener('popstate', (e) => { if (e.state && e.state.viewId) { switchView(e.state.viewId, false); } else { // Handle hash fallback or default home const hash = window.location.hash.replace('#', ''); if (hash) { switchView(hash, false); } else { switchView('hero', false); } } }); // Check initial hash const initialHash = window.location.hash.replace('#', ''); if (initialHash) { switchView(initialHash, false); } }); // --- NAVIGATION ROUTER --- window.switchView = function(viewId, pushHistory = true) { document.querySelectorAll('.view-section').forEach(el => { el.classList.remove('active'); el.style.opacity = 0; }); document.querySelectorAll('.sidebar-link').forEach(el => el.classList.remove('active')); const targetView = document.getElementById('view-' + viewId); if(targetView) { targetView.classList.add('active'); // Elegant GSAP fade in if (window.gsap) { gsap.fromTo(targetView, { opacity: 0, y: 30 }, { opacity: 1, y: 0, duration: 0.6, ease: "power2.out" } ); } else { targetView.style.opacity = 1; } } const link = document.querySelector(`.sidebar-link[data-target="${viewId}"]`); if(link) link.classList.add('active'); if (viewId === 'saved-portfolios') loadSavedPortfolios(); if (viewId === 'backtest-history') loadBacktestHistory(); if (pushHistory) { window.history.pushState({ viewId: viewId }, '', '#' + viewId); } }; // --- GSAP ANIMATIONS --- function initGSAPAnimations() { if (typeof gsap === 'undefined') return; gsap.registerPlugin(ScrollTrigger); // Staggered entry for Model Zoo Cards document.querySelectorAll('.zoo-grid').forEach(grid => { const cards = grid.querySelectorAll('.expandable-card'); if (cards.length === 0) return; gsap.fromTo(cards, { opacity: 0, y: 50 }, { opacity: 1, y: 0, duration: 0.8, stagger: 0.15, ease: "power3.out", scrollTrigger: { trigger: grid, start: "top 85%" } } ); }); } // --- MARKET TICKER --- async function initMarketTicker() { const container = document.getElementById('liveTickerContent'); if (!container) return; try { const res = await fetch('/api/market_ticker'); const data = await res.json(); if(data && Array.isArray(data) && data.length > 0) { let html = ''; // Duplicate array for seamless infinite scrolling const displayData = [...data, ...data, ...data]; displayData.forEach(item => { let colorClass = item.change >= 0 ? 'color: #10b981;' : 'color: #ef4444;'; let sign = item.change >= 0 ? '+' : ''; html += `
${item.name} $${item.price} ${sign}${(item.change*100).toFixed(2)}%
`; });