// Flight Delay Prediction System - JavaScript // Smooth scrolling function scrollToSection(sectionId) { const element = document.getElementById(sectionId); if (element) { element.scrollIntoView({ behavior: 'smooth' }); } } // Initialize charts let hourlyChart, seasonalChart, featureImportanceChart, predictionTrendsChart, airlinePerformanceChart, airportPerformanceChart; // Load insights data async function loadInsights() { try { const response = await fetch('/api/insights'); const data = await response.json(); if (data.error) { console.error('Error loading insights:', data.error); return; } // Update metrics document.getElementById('delay-rate').textContent = data.delay_rate + '%'; document.getElementById('total-flights').textContent = data.total_flights.toLocaleString(); document.getElementById('avg-delay').textContent = data.avg_delay; // Create charts with real data await createHourlyChart(); await createSeasonalChart(); await createFeatureImportanceChart(); await createPredictionTrendsChart(); await createAirlinePerformanceChart(); await createAirportPerformanceChart(); } catch (error) { console.error('Error fetching insights:', error); } } // Create hourly delays chart async function createHourlyChart() { try { const response = await fetch('/api/chart-data/hourly'); const data = await response.json(); if (data.error) { console.error('Error loading hourly data:', data.error); return; } const ctx = document.getElementById('hourly-chart').getContext('2d'); if (hourlyChart) { hourlyChart.destroy(); } hourlyChart = new Chart(ctx, { type: 'bar', data: { labels: data.labels.map(h => h + ':00'), datasets: [{ label: 'Delay Rate by Hour', data: data.data, backgroundColor: '#2F80ED', borderColor: '#2F80ED', borderWidth: 1, borderRadius: 4 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, title: { display: true, text: 'Delays by Departure Hour', color: '#1A2B3C', font: { size: 16, weight: 'bold', family: 'Inter' } } }, scales: { y: { beginAtZero: true, title: { display: true, text: 'Delay Rate (%)', color: '#4A5D73', font: { family: 'Inter' } }, ticks: { color: '#4A5D73', font: { family: 'Inter' } }, grid: { color: 'rgba(74, 93, 115, 0.1)' } }, x: { title: { display: true, text: 'Hour of Day', color: '#4A5D73', font: { family: 'Inter' } }, ticks: { color: '#4A5D73', font: { family: 'Inter' } }, grid: { color: 'rgba(74, 93, 115, 0.1)' } } } } }); } catch (error) { console.error('Error creating hourly chart:', error); } } // Create seasonal delays chart async function createSeasonalChart() { try { const response = await fetch('/api/chart-data/seasonal'); const data = await response.json(); if (data.error) { console.error('Error loading seasonal data:', data.error); return; } const ctx = document.getElementById('seasonal-chart').getContext('2d'); if (seasonalChart) { seasonalChart.destroy(); } seasonalChart = new Chart(ctx, { type: 'pie', data: { labels: data.labels, datasets: [{ data: data.data, backgroundColor: ['#2F80ED', '#27AE60', '#56CCF2', '#F2994A'], borderWidth: 2, borderColor: '#fff', borderRadius: 4 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom', labels: { color: '#1A2B3C', padding: 20, font: { family: 'Inter' } } }, title: { display: true, text: 'Delays by Season', color: '#1A2B3C', font: { size: 16, weight: 'bold', family: 'Inter' } } } } }); } catch (error) { console.error('Error creating seasonal chart:', error); } } // Create feature importance chart async function createFeatureImportanceChart() { try { const response = await fetch('/api/chart-data/feature-importance'); const data = await response.json(); if (data.error) { console.error('Error loading feature importance data:', data.error); return; } const ctx = document.getElementById('feature-importance-chart').getContext('2d'); if (featureImportanceChart) { featureImportanceChart.destroy(); } featureImportanceChart = new Chart(ctx, { type: 'bar', data: { labels: data.labels, datasets: [{ label: 'Feature Importance', data: data.data, backgroundColor: '#2F80ED', borderColor: '#2F80ED', borderWidth: 1, borderRadius: 4 }] }, options: { responsive: true, maintainAspectRatio: false, indexAxis: 'y', plugins: { legend: { display: false }, title: { display: true, text: 'Feature Importance', color: '#1A2B3C', font: { size: 16, weight: 'bold', family: 'Inter' } } }, scales: { x: { title: { display: true, text: 'Importance', color: '#4A5D73', font: { family: 'Inter' } }, ticks: { color: '#4A5D73', font: { family: 'Inter' } }, grid: { color: 'rgba(74, 93, 115, 0.1)' } }, y: { ticks: { color: '#4A5D73', font: { family: 'Inter' } }, grid: { color: 'rgba(74, 93, 115, 0.1)' } } } } }); } catch (error) { console.error('Error creating feature importance chart:', error); } } // Create prediction trends chart async function createPredictionTrendsChart() { try { const response = await fetch('/api/chart-data/prediction-trends'); const data = await response.json(); if (data.error) { console.error('Error loading prediction trends data:', data.error); return; } const ctx = document.getElementById('prediction-trends-chart').getContext('2d'); if (predictionTrendsChart) { predictionTrendsChart.destroy(); } predictionTrendsChart = new Chart(ctx, { type: 'line', data: { labels: data.labels, datasets: [{ label: 'Prediction Confidence', data: data.data, borderColor: '#2F80ED', backgroundColor: 'rgba(47, 128, 237, 0.1)', borderWidth: 3, fill: true, tension: 0.4, pointBackgroundColor: '#2F80ED', pointBorderColor: '#fff', pointBorderWidth: 2, pointRadius: 5, pointHoverRadius: 7 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, title: { display: true, text: 'Prediction Trends Over Time', color: '#1A2B3C', font: { size: 16, weight: 'bold', family: 'Inter' } } }, scales: { y: { beginAtZero: true, max: 100, title: { display: true, text: 'Confidence (%)', color: '#4A5D73', font: { family: 'Inter' } }, ticks: { color: '#4A5D73', font: { family: 'Inter' } }, grid: { color: 'rgba(74, 93, 115, 0.1)' } }, x: { title: { display: true, text: 'Day', color: '#4A5D73', font: { family: 'Inter' } }, ticks: { color: '#4A5D73', font: { family: 'Inter' } }, grid: { color: 'rgba(74, 93, 115, 0.1)' } } } } }); } catch (error) { console.error('Error creating prediction trends chart:', error); } } // Create airline performance chart async function createAirlinePerformanceChart() { try { const response = await fetch('/api/chart-data/airline-performance'); const data = await response.json(); if (data.error) { console.error('Error loading airline performance data:', data.error); return; } const ctx = document.getElementById('airline-performance-chart').getContext('2d'); if (airlinePerformanceChart) { airlinePerformanceChart.destroy(); } airlinePerformanceChart = new Chart(ctx, { type: 'bar', data: { labels: data.labels, datasets: [{ label: 'Delay Rate by Airline', data: data.data, backgroundColor: '#2F80ED', borderColor: '#2F80ED', borderWidth: 1, borderRadius: 4 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, title: { display: true, text: 'Airline Performance', color: '#1A2B3C', font: { size: 16, weight: 'bold', family: 'Inter' } } }, scales: { y: { beginAtZero: true, title: { display: true, text: 'Delay Rate (%)', color: '#4A5D73', font: { family: 'Inter' } }, ticks: { color: '#4A5D73', font: { family: 'Inter' } }, grid: { color: 'rgba(74, 93, 115, 0.1)' } }, x: { title: { display: true, text: 'Airlines', color: '#4A5D73', font: { family: 'Inter', size: 14 } }, ticks: { color: '#4A5D73', font: { family: 'Inter', size: 11 }, maxRotation: 45, minRotation: 0, autoSkip: false, callback: function(value, index, values) { const label = this.getLabelForValue(value); if (label.length > 20) { return label.substring(0, 18) + '...'; } return label; } }, grid: { color: 'rgba(74, 93, 115, 0.1)' } } } } }); } catch (error) { console.error('Error creating airline performance chart:', error); } } // Create airport performance chart async function createAirportPerformanceChart() { try { const response = await fetch('/api/chart-data/airport-performance'); const data = await response.json(); if (data.error) { console.error('Error loading airport performance data:', data.error); return; } const ctx = document.getElementById('airport-performance-chart').getContext('2d'); if (airportPerformanceChart) { airportPerformanceChart.destroy(); } // Prepare data for dual-axis chart const airports = Object.keys(data.top_airports); const delayRates = Object.values(data.delay_rates); const flightVolumes = Object.values(data.top_airports); airportPerformanceChart = new Chart(ctx, { type: 'bar', data: { labels: airports, datasets: [ { label: 'Delay Rate (%)', data: delayRates, backgroundColor: '#EB5757', borderColor: '#EB5757', borderWidth: 1, borderRadius: 4, yAxisID: 'y' }, { label: 'Flight Volume', data: flightVolumes, backgroundColor: '#2F80ED', borderColor: '#2F80ED', borderWidth: 1, borderRadius: 4, yAxisID: 'y1' } ] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: true, position: 'bottom', labels: { color: '#1A2B3C', padding: 20, font: { family: 'Inter' } } }, title: { display: true, text: 'Airport Performance', color: '#1A2B3C', font: { size: 16, weight: 'bold', family: 'Inter' } } }, scales: { y: { type: 'linear', display: true, title: { display: true, text: 'Delay Rate (%)', color: '#4A5D73', font: { family: 'Inter' } }, ticks: { color: '#4A5D73', font: { family: 'Inter' } }, grid: { color: 'rgba(74, 93, 115, 0.1)' } }, y1: { type: 'linear', position: 'right', title: { display: true, text: 'Flight Volume', color: '#4A5D73', font: { family: 'Inter' } }, ticks: { color: '#4A5D73', font: { family: 'Inter' } }, grid: { display: false } }, x: { title: { display: true, text: 'Airports', color: '#4A5D73', font: { family: 'Inter', size: 14 } }, ticks: { color: '#4A5D73', font: { family: 'Inter', size: 11 }, maxRotation: 45, minRotation: 0, autoSkip: false, callback: function(value, index, values) { const label = this.getLabelForValue(value); if (label.length > 25) { return label.substring(0, 23) + '...'; } return label; } }, grid: { color: 'rgba(74, 93, 115, 0.1)' } } } } }); } catch (error) { console.error('Error creating airport performance chart:', error); } } // Create prediction chart for specific prediction async function createPredictionChart(predictionData) { const ctx = document.getElementById('prediction-chart').getContext('2d'); if (window.predictionChart) { window.predictionChart.destroy(); } // Create gauge chart for prediction const prediction = predictionData.prediction; const probability = predictionData.probability; window.predictionChart = new Chart(ctx, { type: 'doughnut', data: { labels: ['On Time', 'Delayed'], datasets: [{ data: [prediction === 0 ? 100 - probability : probability, prediction === 1 ? probability : 100 - probability], backgroundColor: ['#27AE60', '#EB5757'], borderWidth: 2, borderColor: '#fff', borderRadius: 4 }] }, options: { responsive: true, maintainAspectRatio: false, cutout: '70%', plugins: { legend: { display: true, position: 'bottom', labels: { color: '#1A2B3C', padding: 20, font: { family: 'Inter' } } }, title: { display: true, text: `Flight Prediction - ${predictionData.status}`, color: '#1A2B3C', font: { size: 16, weight: 'bold', family: 'Inter' } } } } }); } // Load airlines and airports for form async function loadFormData() { try { // Load airlines const airlinesResponse = await fetch('/api/airlines'); const airlinesData = await airlinesResponse.json(); const airlineSelect = document.getElementById('airline'); if (airlinesData.airlines && !airlinesData.error) { airlinesData.airlines.forEach(airline => { const option = document.createElement('option'); option.value = airline; option.textContent = airline; airlineSelect.appendChild(option); }); } // Load airports const airportsResponse = await fetch('/api/airports'); const airportsData = await airportsResponse.json(); const originSelect = document.getElementById('origin'); const destinationSelect = document.getElementById('destination'); if (airportsData.airports && !airportsData.error) { airportsData.airports.forEach(airport => { const option1 = document.createElement('option'); option1.value = airport; option1.textContent = airport; originSelect.appendChild(option1); const option2 = document.createElement('option'); option2.value = airport; option2.textContent = airport; destinationSelect.appendChild(option2); }); } // Add event listeners for automatic season calculation const flightDateInput = document.getElementById('flight_date'); const originAirportSelect = document.getElementById('origin'); const seasonInput = document.getElementById('season'); // Function to update season based on date and origin function updateSeason() { const flightDate = flightDateInput.value; const origin = originAirportSelect.value; if (flightDate && origin) { // Extract origin airport code for season calculation const originCode = origin.split('(').pop()?.replace(')', '').trim() || origin.split()[0]; // Calculate season based on date and origin const season = calculateSeasonFromDate(flightDate, originCode); seasonInput.value = season; } } flightDateInput.addEventListener('change', updateSeason); originAirportSelect.addEventListener('change', updateSeason); // Set default date to today const today = new Date().toISOString().split('T')[0]; flightDateInput.value = today; } catch (error) { console.error('Error loading form data:', error); } } // Calculate season based on date and origin airport function calculateSeasonFromDate(dateStr, originAirport) { try { const date = new Date(dateStr); const month = date.getMonth() + 1; // 1-12 // Major southern hemisphere airports const southernAirports = ['SYD', 'MEL', 'BNE', 'ADL', 'PER', 'CBR', 'HBA', 'DRW', 'CNS', 'OOL']; // Extract airport code const originCode = originAirport.split('(').pop()?.replace(')', '').trim() || originAirport.split()[0]; const isSouthern = southernAirports.includes(originCode.toUpperCase()); if (isSouthern) { // Southern hemisphere seasons are reversed if ([12, 1, 2].includes(month)) return 'Summer'; if ([3, 4, 5].includes(month)) return 'Fall'; if ([6, 7, 8].includes(month)) return 'Winter'; return 'Spring'; // [9, 10, 11] } else { // Northern hemisphere seasons if ([12, 1, 2].includes(month)) return 'Winter'; if ([3, 4, 5].includes(month)) return 'Spring'; if ([6, 7, 8].includes(month)) return 'Summer'; return 'Fall'; // [9, 10, 11] } } catch (error) { console.error('Error calculating season:', error); return 'Spring'; } } // Handle prediction form submission document.getElementById('prediction-form').addEventListener('submit', async function(e) { e.preventDefault(); console.log('=== FORM SUBMISSION STARTED ==='); const submitButton = this.querySelector('button[type="submit"]'); const originalText = submitButton.textContent; // Get form values directly const airline = document.getElementById('airline').value; const origin = document.getElementById('origin').value; const destination = document.getElementById('destination').value; const departure_hour = document.getElementById('departure_hour').value; const flight_date = document.getElementById('flight_date').value; console.log('Form values:', { airline, origin, destination, departure_hour, flight_date }); // Validate form data if (!airline || !origin || !destination || !departure_hour || !flight_date) { alert('Please fill in all required fields.'); return; } // Show loading state submitButton.textContent = 'Predicting...'; submitButton.disabled = true; try { // Create FormData exactly like the working test function const formData = new FormData(); formData.append('airline', airline); formData.append('origin', origin); formData.append('destination', destination); formData.append('departure_hour', departure_hour); formData.append('flight_date', flight_date); console.log('Sending prediction request...'); const response = await fetch('/api/predict', { method: 'POST', body: formData }); console.log('Response status:', response.status); if (!response.ok) { const errorText = await response.text(); console.error('Server error:', errorText); throw new Error(`Server error: ${response.status} - ${errorText}`); } const result = await response.json(); console.log('Prediction result:', result); if (result.error) { alert('Error: ' + result.error); return; } // Display result directly on webpage displayPredictionResult(result); // Create prediction chart try { await createPredictionChart(result); document.getElementById('prediction-chart-container').style.display = 'block'; } catch (chartError) { console.error('Error creating prediction chart:', chartError); } // Create flight visualization chart try { await createFlightVisualizationChart(result); document.getElementById('flight-visualization-container').style.display = 'block'; } catch (flightChartError) { console.error('Error creating flight visualization chart:', flightChartError); } console.log('=== FORM SUBMISSION COMPLETED ==='); } catch (error) { console.error('Error making prediction:', error); alert('Error making prediction: ' + error.message + '. Please try again.'); } finally { // Reset button submitButton.textContent = originalText; submitButton.disabled = false; } }); // Display prediction result function displayPredictionResult(result) { try { console.log('Displaying prediction result:', result); const resultDiv = document.getElementById('prediction-result'); const probabilityDiv = document.getElementById('result-probability'); const statusDiv = document.getElementById('result-status'); const descriptionDiv = document.getElementById('result-description'); if (!resultDiv || !probabilityDiv || !statusDiv || !descriptionDiv) { console.error('Result display elements not found:', { resultDiv: !!resultDiv, probabilityDiv: !!probabilityDiv, statusDiv: !!statusDiv, descriptionDiv: !!descriptionDiv }); return; } // Update result display probabilityDiv.textContent = (result.probability || 0) + '%'; statusDiv.textContent = result.status || 'Unknown'; // Set color based on prediction if (result.prediction === 1) { probabilityDiv.className = 'result-probability result-delayed'; statusDiv.className = 'result-label result-delayed'; descriptionDiv.textContent = 'High probability of delay. Consider alternative flights or allow extra time.'; } else { probabilityDiv.className = 'result-probability result-ontime'; statusDiv.className = 'result-label result-ontime'; descriptionDiv.textContent = 'Low probability of delay. Flight is likely to be on time.'; } // Show result section resultDiv.style.display = 'block'; console.log('Result displayed successfully'); // Scroll to result setTimeout(() => { resultDiv.scrollIntoView({ behavior: 'smooth', block: 'center' }); }, 100); } catch (error) { console.error('Error displaying prediction result:', error); } } // Create flight visualization chart async function createFlightVisualizationChart(predictionData) { try { const response = await fetch('/api/flight-visualization', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ airline: predictionData.airline, origin: predictionData.origin, destination: predictionData.destination, probability: predictionData.probability, season: predictionData.season }) }); const data = await response.json(); if (data.error) { console.error('Error loading flight visualization data:', data.error); return; } const ctx = document.getElementById('flight-visualization-chart').getContext('2d'); if (window.flightVisualizationChart) { window.flightVisualizationChart.destroy(); } // Create combination chart with flight phases and risk factors window.flightVisualizationChart = new Chart(ctx, { type: 'bar', data: { labels: data.flight_phases, datasets: [ { label: 'Confidence Level', data: data.phase_confidences, backgroundColor: 'rgba(47, 128, 237, 0.8)', borderColor: '#2F80ED', borderWidth: 2, borderRadius: 4, yAxisID: 'y' } ] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: true, position: 'top', labels: { color: '#1A2B3C', font: { family: 'Inter' } } }, title: { display: true, text: `Flight Analysis: ${data.airline} - ${data.origin} to ${data.destination}`, color: '#1A2B3C', font: { size: 16, weight: 'bold', family: 'Inter' } }, subtitle: { display: true, text: `Season: ${data.season} | Overall Delay Probability: ${data.overall_probability}%`, color: '#4A5D73', font: { size: 14, family: 'Inter' } } }, scales: { y: { type: 'linear', display: true, position: 'left', title: { display: true, text: 'Confidence (%)', color: '#4A5D73', font: { family: 'Inter' } }, ticks: { color: '#4A5D73', font: { family: 'Inter' } }, grid: { color: 'rgba(74, 93, 115, 0.1)' }, max: 100 }, x: { title: { display: true, text: 'Flight Phases', color: '#4A5D73', font: { family: 'Inter' } }, ticks: { color: '#4A5D73', font: { family: 'Inter' } }, grid: { color: 'rgba(74, 93, 115, 0.1)' } } } } }); // Create risk factors chart below createRiskFactorsChart(data); } catch (error) { console.error('Error creating flight visualization chart:', error); } } // Create risk factors chart function createRiskFactorsChart(data) { // Create a second chart for risk factors if it doesn't exist const riskContainer = document.getElementById('flight-visualization-container'); // Check if risk chart already exists if (document.getElementById('risk-factors-chart')) { return; } // Create risk factors chart container const riskChartDiv = document.createElement('div'); riskChartDiv.className = 'dashboard-card chart-container'; riskChartDiv.style.gridColumn = '1 / -1'; riskChartDiv.style.marginTop = '30px'; riskChartDiv.innerHTML = ''; riskContainer.appendChild(riskChartDiv); const ctx = document.getElementById('risk-factors-chart').getContext('2d'); if (window.riskFactorsChart) { window.riskFactorsChart.destroy(); } window.riskFactorsChart = new Chart(ctx, { type: 'doughnut', data: { labels: data.risk_factors, datasets: [{ data: data.risk_values, backgroundColor: [ '#EB5757', // Weather Risk - Red '#F2994A', // Traffic Congestion - Orange '#56CCF2', // Airport Delay History - Light Blue '#27AE60', // Airline Performance - Green '#2F80ED' // Time of Day - Blue ], borderWidth: 2, borderColor: '#fff', borderRadius: 4 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: true, position: 'bottom', labels: { color: '#1A2B3C', padding: 20, font: { family: 'Inter' } } }, title: { display: true, text: 'Risk Factors Analysis', color: '#1A2B3C', font: { size: 16, weight: 'bold', family: 'Inter' } } } } }); } // Auto-update flight status function updateFlightStatus() { const onTimeElement = document.querySelector('.status-on-time').parentElement.querySelector('span'); const delayedElement = document.querySelector('.status-delayed').parentElement.querySelector('span'); // Simulate real-time updates const onTimeRate = 75 + Math.random() * 10; const delayedRate = 100 - onTimeRate; onTimeElement.textContent = `On Time: ${onTimeRate.toFixed(1)}%`; delayedElement.textContent = `Delayed: ${delayedRate.toFixed(1)}%`; } // Add parallax effect window.addEventListener('scroll', function() { const scrolled = window.pageYOffset; const parallaxElements = document.querySelectorAll('.parallax-element'); parallaxElements.forEach(element => { const speed = element.dataset.speed || 0.5; element.style.transform = `translateY(${scrolled * speed}px)`; }); // Add parallax to hero section const heroSection = document.querySelector('.hero-section'); if (heroSection) { heroSection.style.transform = `translateY(${scrolled * 0.3}px)`; } }); // Test function to debug prediction API async function testPrediction() { try { console.log('Testing prediction API...'); // Test the test endpoint first const testResponse = await fetch('/api/test'); const testData = await testResponse.json(); console.log('Test endpoint result:', testData); // Test the prediction endpoint with sample data const formData = new FormData(); formData.append('airline', 'Air India'); formData.append('origin', 'Indira Gandhi International Airport (DEL)'); formData.append('destination', 'Chatrapati Shivaji International Airport (BOM)'); formData.append('departure_hour', '10'); formData.append('flight_date', '2024-12-15'); console.log('Sending prediction request...'); const response = await fetch('/api/predict', { method: 'POST', body: formData }); console.log('Response status:', response.status); if (!response.ok) { const errorText = await response.text(); console.error('Server error:', errorText); alert('Server error: ' + response.status + ' - ' + errorText); return; } const result = await response.json(); console.log('Prediction result:', result); if (result.error) { alert('Prediction error: ' + result.error); } else { alert('Prediction successful! Status: ' + result.status + ', Probability: ' + result.probability + '%'); } } catch (error) { console.error('Test error:', error); alert('Test error: ' + error.message); } } // Add smooth scroll behavior document.addEventListener('DOMContentLoaded', function() { // Load form data loadFormData(); // Load insights loadInsights(); // Update flight status every 5 seconds setInterval(updateFlightStatus, 5000); // Add smooth scroll behavior document.documentElement.style.scrollBehavior = 'smooth'; // Add parallax class to elements const heroTitle = document.querySelector('.hero-title'); const heroSubtitle = document.querySelector('.hero-subtitle'); const heroButtons = document.querySelector('.hero-buttons'); if (heroTitle) heroTitle.classList.add('parallax-element'); if (heroSubtitle) heroSubtitle.classList.add('parallax-element'); if (heroButtons) heroButtons.classList.add('parallax-element'); // Set different speeds for different elements if (heroTitle) heroTitle.dataset.speed = '0.2'; if (heroSubtitle) heroSubtitle.dataset.speed = '0.3'; if (heroButtons) heroButtons.dataset.speed = '0.4'; }); // Add intersection observer for animations const observerOptions = { threshold: 0.1, rootMargin: '0px 0px -50px 0px' }; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.style.opacity = '1'; entry.target.style.transform = 'translateY(0)'; } }); }, observerOptions); // Observe all cards for animation document.addEventListener('DOMContentLoaded', function() { const cards = document.querySelectorAll('.feature-card, .dashboard-card, .impact-card'); cards.forEach(card => { card.style.opacity = '0'; card.style.transform = 'translateY(20px)'; card.style.transition = 'opacity 0.6s ease, transform 0.6s ease'; observer.observe(card); }); });