class Utils { static async loadJSON(filePath) { try { const response = await fetch(filePath); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); return data; } catch (error) { console.error(`Error loading JSON from ${filePath}:`, error); return null; } } static saveToLocalStorage(key, data) { try { localStorage.setItem(key, JSON.stringify(data)); return true; } catch (error) { console.error('Error saving to localStorage:', error); return false; } } static loadFromLocalStorage(key) { try { const data = localStorage.getItem(key); return data ? JSON.parse(data) : null; } catch (error) { console.error('Error loading from localStorage:', error); return null; } } static generateId() { return Date.now().toString(36) + Math.random().toString(36).substr(2); } static formatTime(seconds) { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; } static sanitizeHTML(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } static debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } static validateEmail(email) { const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return re.test(email); } static validatePassword(password) { return password.length >= 6; } static calculatePercentage(part, total) { if (total === 0) return 0; return Math.round((part / total) * 100); } static getCurrentDate() { return new Date().toISOString().split('T')[0]; } static formatNumber(num) { return new Intl.NumberFormat('fa-IR').format(num); } static shuffleArray(array) { const shuffled = [...array]; for (let i = shuffled.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; } return shuffled; } static isMobileDevice() { return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); } static async copyToClipboard(text) { try { await navigator.clipboard.writeText(text); return true; } catch (error) { console.error('Failed to copy text:', error); return false; } } static showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.innerHTML = `
${this.sanitizeHTML(message)}
`; notification.style.cssText = ` position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background: ${type === 'error' ? '#fed7d7' : type === 'success' ? '#c6f6d5' : '#bee3f8'}; color: ${type === 'error' ? '#742a2a' : type === 'success' ? '#22543d' : '#2a4365'}; padding: 12px 20px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); z-index: 1000; animation: slideDown 0.3s ease-out; `; document.body.appendChild(notification); notification.querySelector('.notification-close').addEventListener('click', () => { notification.remove(); }); setTimeout(() => { if (notification.parentNode) { notification.remove(); } }, 5000); } } // اضافه کردن استایل برای نوتیفیکیشن const notificationStyles = ` @keyframes slideDown { from { opacity: 0; transform: translateX(-50%) translateY(-20px); } to { opacity: 1; transform: translateX(-50%) translateY(0); } } .notification-content { display: flex; align-items: center; justify-content: space-between; gap: 15px; } .notification-close { background: none; border: none; font-size: 18px; cursor: pointer; padding: 0; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; } `; // تزریق استایل‌ها به صفحه if (!document.querySelector('#notification-styles')) { const styleElement = document.createElement('style'); styleElement.id = 'notification-styles'; styleElement.textContent = notificationStyles; document.head.appendChild(styleElement); }