// 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 = '/';
};