// Page Transition document.addEventListener("DOMContentLoaded", function () { document.body.classList.add("loaded"); // Fade-in page on load }); // Handle link clicks for page transitions document.querySelectorAll("a").forEach(link => { link.addEventListener("click", function (event) { if (this.getAttribute("target") === "_blank") return; event.preventDefault(); // Stop instant navigation let href = this.href; // Create transition element let overlay = document.createElement("div"); overlay.classList.add("transition-overlay"); document.body.appendChild(overlay); // Trigger expanding circle effect setTimeout(() => { overlay.classList.add("expand"); }, 90); // Optional: Clean up the overlay after the animation duration setTimeout(() => { overlay.remove(); }, 1000); // Navigate after transition setTimeout(() => { window.location.href = href; }, 890); }); }); // Fade-In Animation on Scroll document.addEventListener("DOMContentLoaded", function () { const fadeElements = document.querySelectorAll(".fade-in"); const checkVisibility = () => { fadeElements.forEach((element) => { const elementTop = element.getBoundingClientRect().top; const elementBottom = element.getBoundingClientRect().bottom; const isVisible = elementTop < window.innerHeight && elementBottom >= 0; if (isVisible) { element.classList.add("visible"); } }); }; window.addEventListener("scroll", checkVisibility); checkVisibility(); // Check on page load }); // Page transition & dark mode toggle document.addEventListener("DOMContentLoaded", function () { document.body.classList.add("loaded"); const darkToggle = document.getElementById("darkModeToggle"); const toggleIcon = document.querySelector('.toggle-circle .toggle-icon'); function updateToggleIcon() { if (darkToggle && toggleIcon) { if (darkToggle.checked) { toggleIcon.innerHTML = '☾'; // Moon toggleIcon.style.color = "#4a8fdf"; } else { toggleIcon.innerHTML = '☀'; // Sun toggleIcon.style.color = "#FFD600"; } } } if (darkToggle) { darkToggle.checked = true; document.body.classList.add("dark-mode"); updateToggleIcon(); darkToggle.addEventListener("change", () => { document.body.classList.toggle("dark-mode"); updateToggleIcon(); updatePredictionChartColors(); }); } document.querySelectorAll("a").forEach(link => { if (link.getAttribute("target") === "_blank") return; link.addEventListener("click", function (event) { event.preventDefault(); const overlay = document.createElement("div"); overlay.classList.add("transition-overlay"); document.body.appendChild(overlay); setTimeout(() => overlay.classList.add("active"), 90); setTimeout(() => overlay.remove(), 1000); setTimeout(() => window.location.href = this.href, 890); }); }); // Set min date for prediction input const today = new Date(); const minDate = new Date(today); minDate.setDate(today.getDate() + 6); const maxDate = new Date(today); maxDate.setMonth(today.getMonth() + 1); function formatDate(d) { const yyyy = d.getFullYear(); const mm = String(d.getMonth() + 1).padStart(2, '0'); const dd = String(d.getDate()).padStart(2, '0'); return `${yyyy}-${mm}-${dd}`; } const predictionDateInput = document.getElementById('prediction-date'); predictionDateInput.min = formatDate(minDate); predictionDateInput.max = formatDate(maxDate); }); document.addEventListener("DOMContentLoaded", function () { fetch("/get-companies") .then(response => response.json()) .then(data => { const select = document.getElementById("stock-select"); const seen = new Set(); // Track unique tickers data.forEach(company => { const ticker = company["Yahoo Finance Ticker"]; const name = company["Company Name"]; // Exclude NIFTY and SENSEX tickers and duplicates if (ticker !== "^NSEI" && ticker !== "^BSESN" && !seen.has(ticker)) { const option = document.createElement("option"); option.value = ticker; option.textContent = name; select.appendChild(option); seen.add(ticker); } }); }) .catch(error => console.error("Error fetching companies:", error)); }); let predictionChart = null; function handleEpochsChange(select) { const customInput = document.getElementById('custom-epochs'); if (select.value === 'custom') { customInput.style.display = 'block'; customInput.focus(); } else { customInput.style.display = 'none'; } }window.handleEpochsChange = handleEpochsChange; // Listen for the "Predict" button click and route prediction request based on radio button document.getElementById('predict-btn').addEventListener('click', async function (e) { e.preventDefault(); const stockSymbol = document.getElementById('stock-select').value; const predictionType = document.querySelector('input[name="data-source"]:checked').value; const predictionDate = document.getElementById('prediction-date').value; if (!stockSymbol) { showMessage("Please select a stock", true); return; } if (!predictionDate) { showMessage("Please select a prediction date", true); return; } // Date validation const selected = new Date(predictionDate); const today = new Date(); const min = new Date(today); min.setDate(today.getDate() + 6); // 6 days minimum const max = new Date(today); max.setMonth(today.getMonth() + 1); selected.setHours(0,0,0,0); min.setHours(0,0,0,0); max.setHours(0,0,0,0); if (selected < min || selected > max) { showMessage("Prediction date must be at least 6 days from today and at most 1 month from today.", true); return; } let epochs; const epochsSelect = document.getElementById('epochs'); if (epochsSelect.value === 'custom') { epochs = document.getElementById('custom-epochs').value || 100; } else { epochs = epochsSelect.value; } // Show loading overlay and start progress document.getElementById('loading-overlay').style.display = "flex"; updateProgress(0); this.disabled = true; this.textContent = "Predicting..."; let payload = { ticker: stockSymbol, type: predictionType, prediction_date: predictionDate, epochs: epochs }; // Set progress duration based on prediction type let progress = 0; let intervalMs; let maxProgress = 95; if (predictionType === "both") { // Double the time for both pipelines (~130 seconds) intervalMs = 51000; // 100% / 130s = ~1% per 1.3s } else if (predictionType === "news-sentiment") { // About 79 seconds for sentiment pipeline intervalMs = 27000; // 100% / 79s = ~1% per 0.79s } else { intervalMs = 18000; // ~65 seconds for single pipeline } let progressInterval = setInterval(() => { if (progress < maxProgress) { progress += 1; updateProgress(progress); } }, intervalMs); try { const response = await fetch('/predict-result', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); clearInterval(progressInterval); updateProgress(100); const result = await response.json(); setTimeout(() => { document.getElementById('loading-overlay').style.display = "none"; }, 400); if (response.ok) { displayPredictionResults(result); } else { showMessage(result.error || "Prediction failed", true); } } catch (error) { clearInterval(progressInterval); document.getElementById('loading-overlay').style.display = "none"; showMessage("An error occurred", true); } this.disabled = false; this.textContent = "Predict Stock Price"; }); function updateProgress(percent) { document.getElementById('progress-text').textContent = `Loading... ${percent}%`; } function displayPredictionResults(prediction) { // If both, render two graphs in separate areas with space if (prediction.historical && prediction.sentiment) { document.getElementById('result-section').style.display = "block"; document.getElementById('result-stock-name').textContent = prediction.historical.stock; document.getElementById('predicted-price').textContent = prediction.historical.predictedPrice; // Clear and create two separate chart areas with spacing const chartContainer = document.querySelector('.chart-container'); chartContainer.innerHTML = `

Historical Only

With Sentiment

`; renderPredictionChart(prediction.historical, 'prediction-chart-historical', 'Historical Only'); renderPredictionChart(prediction.sentiment, 'prediction-chart-sentiment', 'With Sentiment'); } else { // Single pipeline as before document.getElementById('result-stock-name').textContent = prediction.stock; document.getElementById('predicted-price').textContent = prediction.predictedPrice; document.getElementById('result-section').style.display = "block"; renderPredictionChart(prediction, 'prediction-chart'); } } function renderPredictionChart(prediction, chartId = 'prediction-chart', customTitle = null) { const data = []; // Train data (from historicalData) const trainLabels = prediction.historicalData.map(d => d.date); const trainValues = prediction.historicalData.map(d => d.price); data.push({ x: trainLabels, y: trainValues, mode: 'lines', name: 'Train Data', fill: 'tozeroy', line: { color: 'rgba(74,143,223,1)' } }); // Actual price (validation/test) from actualVsPredicted const validLabels = prediction.actualVsPredicted.map(d => d.date); const actualValues = prediction.actualVsPredicted.map(d => d.actual); data.push({ x: validLabels, y: actualValues, mode: 'lines', name: 'Actual Price', fill: 'tozeroy', line: { color: 'green', width: 2 } }); // Predicted price (validation/test) from actualVsPredicted const predictedValues = prediction.actualVsPredicted.map(d => d.predicted); data.push({ x: validLabels, y: predictedValues, mode: 'lines', name: 'Predicted Price', fill: 'tozeroy', line: { color: 'red', width: 3, dash: 'dot' } }); // Future predictions if (prediction.futureDates && prediction.futurePrices) { data.push({ x: prediction.futureDates, y: prediction.futurePrices, mode: 'lines+markers', name: 'Future Predictions', fill: 'tozeroy', line: { color: 'orange', width: 2, dash: 'dash' }, marker: { size: 8, color: 'orange' } }); } // Buy signals if (prediction.buySignals && prediction.buySignals.points) { const buyPoints = prediction.buySignals.points; const buyReasons = prediction.buySignals.reasons || []; const buyIndices = prediction.buyIndices || Array.from({length: buyPoints.length}, (_, i) => i); const buyDates = buyIndices.map(idx => prediction.futureDates[idx] || ''); data.push({ x: buyDates, y: buyPoints, mode: 'markers', name: 'Buy Signals', marker: { color: 'cyan', size: 12, symbol: 'star', line: { width: 2, color: 'black' } }, text: buyReasons, hovertemplate: 'Buy Signal
Date: %{x}
Price: ₹%{y:,.2f}
Reason: %{text}' }); } // Layout (unchanged) const getTextColor = () => '#A0AEC0'; const getGridColor = () => 'rgba(105,123,147,0.4)'; const getBgColor = () => 'rgba(19,27,43,0.0)'; const layout = { title: customTitle || 'Stock Price Prediction with Future Forecast & Buy Signals', xaxis: { title: 'Date', color: getTextColor(), gridcolor: getGridColor(), spikecolor: "rgba(105,123,147,255)", spikedash: "solid", spikethickness: -2 }, yaxis: { title: 'Stock Price', color: getTextColor(), gridcolor: getGridColor(), tickprefix: '₹', tickformat: ',', spikecolor: "rgba(105,123,149,255)", spikedash: "solid", spikethickness: -2, zeroline: false }, paper_bgcolor: getBgColor(), plot_bgcolor: getBgColor(), font: { color: getTextColor() }, margin: { t: 50, b: 50, l: 50, r: 20 }, legend: { x: 0, y: 1 }, hovermode: "closest" }; Plotly.newPlot(chartId, data, layout, { responsive: true }); } window.renderPredictionChart = renderPredictionChart; function updatePredictionChartColors() { const layoutUpdate = { paper_bgcolor: getBgColor(), plot_bgcolor: getBgColor(), font: { color: getTextColor() }, xaxis: { color: getTextColor(), gridcolor: getGridColor() }, yaxis: { color: getTextColor(), gridcolor: getGridColor() } }; Plotly.relayout('prediction-chart', layoutUpdate); } function getTextColor() { return document.body.classList.contains("dark-mode") ? '#fff' : '#333'; } function getGridColor() { return document.body.classList.contains("dark-mode") ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.05)'; } function getBgColor() { return document.body.classList.contains("dark-mode") ? '#1e2a38' : '#fff'; } function showMessage(message, isError = false) { let box = document.getElementById("message-box"); if (!box) { box = document.createElement("div"); box.id = "message-box"; // Place above the Predict button const btn = document.getElementById('predict-btn'); btn.parentNode.insertBefore(box, btn); } box.style.position = "static"; box.style.marginBottom = "16px"; box.style.padding = "10px 18px"; box.style.borderRadius = "6px"; box.style.zIndex = "1"; box.style.fontSize = "15px"; box.style.boxShadow = "0 4px 12px rgba(0,0,0,0.08)"; box.style.backgroundColor = isError ? "#ffe6e6" : "#e0f7fa"; box.style.color = isError ? "#c62828" : "#00796b"; box.style.border = `1px solid ${isError ? "#c62828" : "#00796b"}`; box.style.display = "inline-block"; box.style.maxWidth = "320px"; box.style.textAlign = "center"; box.style.whiteSpace = "normal"; box.style.wordBreak = "break-word"; box.style.marginLeft = "auto"; box.style.marginRight = "auto"; box.textContent = message; setTimeout(() => { box.style.display = "none"; }, 4000); } // Disclaimer close logic document.addEventListener("DOMContentLoaded", function() { const closeBtn = document.getElementById("close-disclaimer"); if (closeBtn) { closeBtn.onclick = function() { document.getElementById("disclaimer-overlay").style.display = "none"; }; } }); document.addEventListener("DOMContentLoaded", function () { // Hide disclaimer by default document.getElementById("disclaimer-overlay").style.display = "none"; function showDisclaimer() { document.getElementById("disclaimer-overlay").style.display = "flex"; } // Only show tutorial if not seen before if (!localStorage.getItem("predictorTutorialSeen")) { setTimeout(() => { introJs().setOptions({ nextLabel: 'Next', prevLabel: 'Back', skipLabel: 'Skip', doneLabel: 'Done', overlayOpacity: 0.7, showProgress: true, tooltipClass: 'custom-introjs-tooltip' }).oncomplete(function() { localStorage.setItem("predictorTutorialSeen", "yes"); showDisclaimer(); }).onexit(function() { localStorage.setItem("predictorTutorialSeen", "yes"); showDisclaimer(); }).start(); }, 600); // Wait for page to load } else { // If tutorial already seen, show disclaimer immediately showDisclaimer(); } // Disclaimer close logic const closeBtn = document.getElementById("close-disclaimer"); if (closeBtn) { closeBtn.onclick = function() { document.getElementById("disclaimer-overlay").style.display = "none"; }; } }); document.addEventListener('DOMContentLoaded', function () { const sentimentContainer = document.getElementById('news-sentiment'); const sentimentText = document.getElementById('sentiment-text'); const radioButtons = document.querySelectorAll('input[name="data-source"]'); // Function to map sentiment values to text function mapSentimentValueToText(value) { switch (value) { case 0: return "Negative"; case 1: return "Neutral"; case 2: return "Positive"; default: return "Unknown"; } } // Function to fetch and display news sentiment async function fetchNewsSentiment(stockSymbol) { try { const response = await fetch(`/api/news-sentiment?ticker=${stockSymbol}`); const data = await response.json(); if (response.ok && data.sentiment !== undefined) { const sentimentTextValue = mapSentimentValueToText(data.sentiment); sentimentText.textContent = `Sentiment: ${sentimentTextValue}`; } else { sentimentText.textContent = 'No sentiment data available.'; } } catch (error) { sentimentText.textContent = 'Error fetching sentiment data.'; console.error('Error fetching sentiment:', error); } } // Event listener for radio button changes radioButtons.forEach(radio => { radio.addEventListener('change', function () { const stockSymbol = document.getElementById('stock-select').value; if ((this.value === 'news-sentiment' || this.value === 'both') && stockSymbol) { sentimentContainer.style.display = 'block'; fetchNewsSentiment(stockSymbol); } else { sentimentContainer.style.display = 'none'; } }); }); // Trigger sentiment fetch after prediction document.getElementById('predict-btn').addEventListener('click', function () { const selectedRadio = document.querySelector('input[name="data-source"]:checked'); const stockSymbol = document.getElementById('stock-select').value; if (selectedRadio && (selectedRadio.value === 'news-sentiment' || selectedRadio.value === 'both')) { sentimentContainer.style.display = 'block'; fetchNewsSentiment(stockSymbol); } else { sentimentContainer.style.display = 'none'; } }); }); import { initializeNewsletterForm } from './newsletter.js'; document.addEventListener("DOMContentLoaded", function () { initializeNewsletterForm(); // Other page-specific code... });