// Notification and Progress System class NotificationManager { constructor() { this.notifications = []; this.notificationId = 0; } show(title, message, type = 'info', duration = 5000) { const id = this.notificationId++; // Create notification element const notification = document.createElement('div'); notification.className = `notification-toast ${type}`; notification.id = `notification-${id}`; // Icon based on type let icon = ''; if (type === 'success') { icon = ''; } else if (type === 'error') { icon = ''; } else { icon = ''; } notification.innerHTML = ` ${icon}
${title}
${message}
`; document.body.appendChild(notification); this.notifications.push({ id, element: notification }); // Auto-close after duration if (duration > 0) { setTimeout(() => this.close(id), duration); } // DO NOT show browser notifications - they overlap with our in-app notifications return id; } close(id) { const notification = this.notifications.find(n => n.id === id); if (notification) { notification.element.style.animation = 'slideOut 0.3s ease'; setTimeout(() => { notification.element.remove(); this.notifications = this.notifications.filter(n => n.id !== id); }, 300); } } success(title, message, duration = 5000) { return this.show(title, message, 'success', duration); } error(title, message, duration = 7000) { return this.show(title, message, 'error', duration); } info(title, message, duration = 5000) { return this.show(title, message, 'info', duration); } } class ProgressTracker { constructor() { this.startTime = null; this.stages = []; this.currentStage = 0; } start(stages = ['Processing...']) { this.startTime = Date.now(); this.stages = stages; this.currentStage = 0; this.updateProgress(0, stages[0]); } nextStage() { this.currentStage++; if (this.currentStage < this.stages.length) { const progress = (this.currentStage / this.stages.length) * 100; this.updateProgress(progress, this.stages[this.currentStage]); } } updateProgress(percent, message = null) { const progressFill = document.getElementById('progressFill'); const progressText = document.getElementById('progressText'); const loadingMessage = document.getElementById('loadingMessage'); const etaText = document.getElementById('etaText'); if (progressFill) { progressFill.style.width = `${percent}%`; } if (progressText) { progressText.textContent = `${Math.round(percent)}%`; } if (message && loadingMessage) { loadingMessage.textContent = message; } // Calculate ETA if (this.startTime && percent > 0 && percent < 100) { const elapsed = Date.now() - this.startTime; const total = (elapsed / percent) * 100; const remaining = total - elapsed; if (etaText && remaining > 0) { const seconds = Math.ceil(remaining / 1000); if (seconds < 60) { etaText.textContent = `ETA: ${seconds}s`; } else { const minutes = Math.floor(seconds / 60); const secs = seconds % 60; etaText.textContent = `ETA: ${minutes}m ${secs}s`; } } } else if (etaText) { etaText.textContent = ''; } } complete(message = 'Complete!') { this.updateProgress(100, message); // Clear ETA const etaText = document.getElementById('etaText'); if (etaText) { etaText.textContent = ''; } } reset() { this.startTime = null; this.stages = []; this.currentStage = 0; this.updateProgress(0, ''); } } // Global instances const notificationManager = new NotificationManager(); const progressTracker = new ProgressTracker(); // DO NOT request notification permission - we only use in-app notifications // Add slideOut animation const style = document.createElement('style'); style.textContent = ` @keyframes slideOut { from { transform: translateX(0); opacity: 1; } to { transform: translateX(400px); opacity: 0; } } `; document.head.appendChild(style);