// Global Variables let debounceTimer; // --- INITIALIZATION --- document.addEventListener('DOMContentLoaded', () => { const accessKey = sessionStorage.getItem('accessKey'); if (!accessKey) { window.location.href = '/'; return; } // Key is deliberately persisted across page reloads and back/forward navigation // Expandable Cards Logic document.addEventListener('click', (e) => { const card = e.target.closest('.expandable-card'); if (card) { // Close others (optional accordion style) or just toggle this one: card.classList.toggle('expanded'); } }); // 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(); initHeroRadar(); init3DUniverse(); 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 preview listeners ONLY to explicit change events, not typing/sliding const inputs = document.querySelectorAll('#portfolioForm input, #portfolioForm select'); inputs.forEach(input => { // Removed the 'input' event listener to disable live auto-updating }); // Removed explicit Preview Button since live panel is deleted // 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 }); }); // Main Form Submit (Full Report) const form = document.getElementById('portfolioForm'); if (form) { form.addEventListener('submit', async (e) => { e.preventDefault(); await generateFullReport(); }); } // 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('.nav-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(`.nav-link[data-target="${viewId}"]`); if(link) link.classList.add('active'); if (pushHistory) { window.history.pushState({ viewId: viewId }, '', '#' + viewId); } }; // --- GSAP ANIMATIONS --- function initGSAPAnimations() { if (typeof gsap === 'undefined') return; if (typeof ScrollTrigger !== 'undefined') { 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() { try { const res = await fetch('/api/market_ticker'); const data = await res.json(); const container = document.getElementById('liveTickerContent'); if(data && data.length > 0) { let html = ''; // Duplicate array for seamless infinite scrolling const displayData = [...data, ...data, ...data]; displayData.forEach(item => { const colorClass = item.change >= 0 ? 'ticker-positive' : 'ticker-negative'; const sign = item.change > 0 ? '+' : ''; html += `
${item.name} ${item.price.toLocaleString()} ${sign}${item.change}%
`; }); container.innerHTML = html; } else { container.innerHTML = "Market data unavailable"; } } catch(e) { console.error("Ticker fetch failed:", e); } } // --- PAYLOAD GENERATOR --- function getPayload() { let custom_constraints = []; const advInput = document.getElementById('custom_constraints_input'); if (advInput && advInput.value.trim() !== '') { const lines = advInput.value.split('\n'); lines.forEach(line => { const parts = line.split(',').map(p => p.trim()); if (parts.length === 3) { let asset = parts[0]; let direction = parts[1].toLowerCase(); let limit = parseFloat(parts[2]); if (!isNaN(limit)) { custom_constraints.push({ asset: asset, direction: direction, limit: limit / 100.0 }); } } }); } return { tickers: document.getElementById('tickers').value.split(',').map(t => t.trim()).filter(t => t), capital: parseFloat(document.getElementById('capital').value) || 100000, risk_input: parseInt(document.getElementById('risk').value), model: parseInt(document.getElementById('model').value), allocation_engine: parseInt(document.getElementById('allocation_engine').value), allow_shorting: document.getElementById('allow_shorting').checked, tax_enabled: document.getElementById('tax_enabled').checked, garch_enabled: document.getElementById('garch_enabled').checked, custom_constraints: custom_constraints }; } // --- HERO RADAR CHART --- function initHeroRadar() { const ctx = document.getElementById('heroRadarChart'); if (!ctx) return; const data = { labels: ['Value', 'Momentum', 'Quality', 'Low Volatility', 'Yield'], datasets: [{ label: 'Current Regime Exposure', data: [65, 85, 40, 70, 50], backgroundColor: 'rgba(96, 165, 250, 0.2)', borderColor: 'rgba(96, 165, 250, 1)', pointBackgroundColor: 'rgba(96, 165, 250, 1)', pointBorderColor: '#fff', pointHoverBackgroundColor: '#fff', pointHoverBorderColor: 'rgba(96, 165, 250, 1)' }] }; const config = { type: 'radar', data: data, options: { responsive: true, maintainAspectRatio: false, scales: { r: { angleLines: { color: 'rgba(255, 255, 255, 0.1)' }, grid: { color: 'rgba(255, 255, 255, 0.1)' }, pointLabels: { color: '#94a3b8', font: { size: 11, family: 'Inter' } }, ticks: { display: false, max: 100, min: 0 } } }, plugins: { legend: { display: false } } } }; const chart = new Chart(ctx, config); // Simulate dynamic factor shifting setInterval(() => { chart.data.datasets[0].data = chart.data.datasets[0].data.map(val => { let shift = (Math.random() - 0.5) * 15; return Math.max(10, Math.min(100, val + shift)); }); chart.update('active'); }, 3000); } // --- FULL REPORT GENERATION --- async function generateFullReport() { const payload = getPayload(); const accessKey = sessionStorage.getItem('accessKey') || ""; // Trigger Cinematic Matrix Loader const matrixLoader = document.getElementById('matrix-loader'); const matrixLogs = document.getElementById('matrix-logs'); const matrixProgress = document.getElementById('matrix-progress'); matrixLoader.style.display = 'flex'; matrixLogs.innerHTML = ''; matrixProgress.style.width = '0%'; document.body.classList.add('engine-running'); const steps = [ "Initializing quantum-inspired core engine...", "Fetching multi-asset historical data vectors...", "Computing eigen-decomposition of covariance matrix Σ...", "Simulating 1,500 Monte Carlo forward paths...", "Applying dynamic L1-Norm tax constraints...", "Identifying Hidden Markov Model market regimes...", "Converging non-linear convex solver...", "Compiling institutional HTML report matrix..." ]; const infiniteSteps = [ "Calculating gradients...", "Rebalancing tensors...", "Analyzing tail risks...", "Synchronizing with Hugging Face ML Cluster...", "Backpropagating loss function...", "Filtering noise from alpha signals...", "Executing stochastic bounds check...", "Optimizing L2 regularization weights..." ]; let logIdx = 0; let infiniteIdx = 0; const interval = setInterval(() => { const el = document.createElement('div'); if(logIdx < steps.length) { el.innerHTML = `> ${steps[logIdx]}`; matrixProgress.style.width = `${((logIdx + 1) / steps.length) * 100}%`; logIdx++; } else { // Infinite loading loop el.innerHTML = `> [ML CLUSTER] ${infiniteSteps[infiniteIdx % infiniteSteps.length]} `; infiniteIdx++; // Keep progress bar pulsing matrixProgress.style.opacity = (Math.sin(infiniteIdx) * 0.5 + 0.5).toString(); } matrixLogs.appendChild(el); // Auto scroll const scrollAmt = Math.max(0, matrixLogs.children.length - 6); if (scrollAmt > 0) { matrixLogs.style.transform = `translateY(-${scrollAmt * 20}px)`; } }, 800); // Cinematic log streaming try { const res = await fetch('/api/generate', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Access-Key': accessKey }, body: JSON.stringify(payload) }); if (!res.ok) { clearInterval(interval); let errTxt = "Generation Failed"; try { const errData = await res.json(); errTxt = errData.detail || errTxt; } catch (e) {} matrixLogs.innerHTML += `
> ERROR: ${errTxt}
`; setTimeout(() => { matrixLoader.style.display = 'none'; document.body.classList.remove('engine-running'); }, 4000); return; } const data = await res.json(); if (data.status !== "queued") { clearInterval(interval); matrixLogs.innerHTML += `
> Unexpected server response.
`; setTimeout(() => { matrixLoader.style.display = 'none'; document.body.classList.remove('engine-running'); }, 3000); return; } const taskId = data.task_id; matrixLogs.innerHTML += `
> Job ${taskId.substring(0,8)} queued. Polling matrix cluster...
`; const pollInterval = setInterval(async () => { try { const statusRes = await fetch(`/api/status/${taskId}`, { headers: { 'X-Access-Key': accessKey } }); if (!statusRes.ok) { clearInterval(pollInterval); clearInterval(interval); matrixLogs.innerHTML += `
> Polling failed.
`; setTimeout(() => { matrixLoader.style.display = 'none'; document.body.classList.remove('engine-running'); }, 3000); return; } const statusData = await statusRes.json(); if (statusData.status === "completed") { clearInterval(pollInterval); clearInterval(interval); matrixProgress.style.width = '100%'; matrixLogs.innerHTML += `
> MATRIX OPTIMIZATION COMPLETE. REDIRECTING...
`; setTimeout(() => { matrixLoader.style.display = 'none'; document.body.classList.remove('engine-running'); window.location.href = "/report"; }, 1500); } else if (statusData.status === "error") { clearInterval(pollInterval); clearInterval(interval); matrixLogs.innerHTML += `
> CRITICAL ERROR: ${statusData.message}
`; setTimeout(() => { matrixLoader.style.display = 'none'; document.body.classList.remove('engine-running'); }, 4000); } } catch (err) { // Ignore network blips during polling } }, 2000); } catch(err) { clearInterval(interval); matrixLogs.innerHTML += `
> CRITICAL ERROR: Network failure.
`; setTimeout(() => { matrixLoader.style.display = 'none'; document.body.classList.remove('engine-running'); }, 4000); } } // --- WIZARD LOGIC --- window.nextWizardStep = function(step) { document.querySelectorAll('.wizard-step').forEach(el => el.style.display = 'none'); const target = document.getElementById('wizardStep' + step); if(target) { target.style.display = 'block'; } }; window.runWizard = async function() { const macro = document.getElementById('wizardMacro').value; const reaction = document.getElementById('wizardReaction').value; const basket = document.getElementById('wizardBasket').value; // Auto-fill the Sandbox form under the hood document.getElementById('tickers').value = basket; let risk = 5; if (reaction === 'buy') risk = 2; if (reaction === 'hold') risk = 5; if (reaction === 'sell') risk = 8; document.getElementById('risk').value = risk; document.getElementById('riskVal').textContent = risk; let model = 5; // XGBoost default if (macro === 'growth') model = 4; // Fama-French if (macro === 'recession') model = 7; // HMM if (macro === 'inflation') model = 3; // Bayesian Shrinkage document.getElementById('model').value = model.toString(); // Switch to Sandbox view const modal = document.getElementById('wizardOverlay'); if(modal) modal.style.display = 'none'; switchView('sandbox'); // Trigger the full report generation automatically await generateFullReport(); }; // 3D Universe logic removed per user request (Restored Vanta.js NET) // --- REPORT FRAME LOGIC --- window.openReportFrame = async function() { const reportContainer = document.getElementById('reportContainer'); const reportView = document.getElementById('report-view'); // Check if report actually exists before opening iframe try { const checkRes = await fetch('/report'); if (!checkRes.ok) { alert("Report generation failed or returned a blank response. Check server logs."); return; } } catch(e) { alert("Error fetching report."); return; } document.querySelector('.main-content').style.display = 'none'; document.querySelector('nav').style.display = 'none'; document.querySelector('.market-ticker-bar').style.display = 'none'; reportContainer.style.display = 'block'; reportView.src = '/report?t=' + new Date().getTime(); }; window.closeReport = function() { document.getElementById('reportContainer').style.display = 'none'; document.querySelector('.main-content').style.display = 'block'; document.querySelector('nav').style.display = 'flex'; document.querySelector('.market-ticker-bar').style.display = 'flex'; }; // Ambient Background relies entirely on Vanta JS now. // --- AUTHENTICATION FLOW --- window.logout = function() { sessionStorage.removeItem('accessKey'); window.location.href = '/'; };