diff --git a/public/index.html b/public/index.html index 456af83..9c97028 100644 --- a/public/index.html +++ b/public/index.html @@ -6,6 +6,24 @@ School CRM - Управление задачами + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/public/loading-end.js b/public/loading-end.js new file mode 100644 index 0000000..0d4a132 --- /dev/null +++ b/public/loading-end.js @@ -0,0 +1,29 @@ +// loading-end.js - Загружается последним +(function() { + // Отмечаем, что скрипт загружен + if (window.loadingScripts) { + window.loadingScripts.loaded++; + window.loadingScripts.updateProgress(); + + // Дополнительная проверка на ошибки загрузки + if (window.loadingScripts.errors.length > 0) { + console.warn('Некоторые скрипты не загрузились:', window.loadingScripts.errors); + + // Показываем предупреждение, но не блокируем интерфейс + const statusDisplay = document.getElementById('loading-status'); + if (statusDisplay) { + statusDisplay.innerHTML = '⚠️ Загружено с предупреждениями'; + statusDisplay.style.color = '#ffd700'; + } + } + } + + // Проверяем, что все скрипты действительно загружены + setTimeout(() => { + if (window.loadingScripts && window.loadingScripts.loaded < window.loadingScripts.total) { + // Принудительно завершаем загрузку через 10 секунд + window.loadingScripts.loaded = window.loadingScripts.total; + window.loadingScripts.updateProgress(); + } + }, 10000); +})(); \ No newline at end of file diff --git a/public/loading-start.js b/public/loading-start.js new file mode 100644 index 0000000..1925bf1 --- /dev/null +++ b/public/loading-start.js @@ -0,0 +1,848 @@ +// loading-start.js - Загружается первым в +(function() { + // Конфигурация + const CONFIG = { + maxRetries: 3, + retryDelay: 2000, + timeout: 10000, + showWarnings: true, + maxLoadingTime: 15000, // Максимум 15 секунд + minLoadingTime: 5000 // Минимум 5 секунд + }; + + // Генерация случайных цветов для градиента + function getRandomGradient() { + // Базовая палитра цветов для школы/образования + const colorPalettes = [ + // Сине-фиолетовая гамма + { start: '#667eea', end: '#764ba2' }, + { start: '#6B73FF', end: '#000DFF' }, + { start: '#5f72bd', end: '#9b23ea' }, + + // Зелено-голубая гамма + { start: '#11998e', end: '#38ef7d' }, + { start: '#0BA360', end: '#3CBBB2' }, + { start: '#1D976C', end: '#93F9B9' }, + + // Оранжево-розовая гамма + { start: '#f46b45', end: '#eea849' }, + { start: '#FF512F', end: '#DD2476' }, + { start: '#F09819', end: '#EDDE5D' }, + + // Красная гамма + { start: '#cb2d3e', end: '#ef473a' }, + { start: '#FF416C', end: '#FF4B2B' }, + { start: '#DC2424', end: '#4A569D' }, + + // Фиолетовая гамма + { start: '#8E2DE2', end: '#4A00E0' }, + { start: '#A770EF', end: '#CF8BF3' }, + { start: '#7F00FF', end: '#E100FF' }, + + // Морская гамма + { start: '#00B4DB', end: '#0083B0' }, + { start: '#00C9FF', end: '#92FE9D' }, + { start: '#1FA2FF', end: '#12D8FA' }, + + // Осенняя гамма + { start: '#FC4A1A', end: '#F7B733' }, + { start: '#ED213A', end: '#93291E' }, + { start: '#F12711', end: '#F5AF19' }, + + // Ночная гамма + { start: '#141E30', end: '#243B55' }, + { start: '#0F2027', end: '#203A43' }, + { start: '#1c1c1c', end: '#3a3a3a' }, + + // Пастельная гамма + { start: '#a8c0ff', end: '#3f2b96' }, + { start: '#c84e89', end: '#F15F79' }, + { start: '#b993d0', end: '#8ca6db' }, + + // Школьная гамма + { start: '#56ab2f', end: '#a8e063' }, + { start: '#1D976C', end: '#93F9B9' }, + { start: '#4CA1AF', end: '#2C3E50' } + ]; + + // Случайная палитра + const palette = colorPalettes[Math.floor(Math.random() * colorPalettes.length)]; + + // Случайное смещение градиента + const directions = [ + '135deg', '90deg', '45deg', '180deg', '225deg', '270deg', '315deg' + ]; + const direction = directions[Math.floor(Math.random() * directions.length)]; + + return { + background: `linear-gradient(${direction}, ${palette.start}, ${palette.end})`, + start: palette.start, + end: palette.end, + direction: direction + }; + } + + // Генерация случайного цвета для прогресс-бара + function getRandomProgressColor() { + const colors = [ + '#fff', '#ffd700', '#00ff87', '#00ffff', '#ff69b4', '#ffa500', + '#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#ffeaa7', '#dfe6e9' + ]; + return colors[Math.floor(Math.random() * colors.length)]; + } + + // Генерация случайного цвета для текста + function getRandomTextColor() { + const colors = [ + 'white', '#f0f0f0', '#ffeaa7', '#dfe6e9', '#b2bec3', '#ffffff' + ]; + return colors[Math.floor(Math.random() * colors.length)]; + } + + // Получаем случайные цвета для этой загрузки + const gradientColors = getRandomGradient(); + const progressColor = getRandomProgressColor(); + const textColor = getRandomTextColor(); + const accentColor = getRandomProgressColor(); + + // Статусы для динамического отображения + const LOADING_STATUSES = [ + { emoji: '📦', text: 'Загрузка компонентов...', weight: 10 }, + { emoji: '🔧', text: 'Настройка модулей...', weight: 15 }, + { emoji: '⚙️', text: 'Инициализация системы...', weight: 20 }, + { emoji: '🔌', text: 'Подключение к серверу...', weight: 25 }, + { emoji: '📊', text: 'Загрузка данных...', weight: 30 }, + { emoji: '🧩', text: 'Сборка интерфейса...', weight: 35 }, + { emoji: '🎨', text: 'Отрисовка элементов...', weight: 40 }, + { emoji: '🔐', text: 'Проверка безопасности...', weight: 45 }, + { emoji: '📁', text: 'Загрузка файлов...', weight: 50 }, + { emoji: '🔄', text: 'Синхронизация...', weight: 55 }, + { emoji: '🧪', text: 'Тестирование модулей...', weight: 60 }, + { emoji: '📝', text: 'Подготовка задач...', weight: 65 }, + { emoji: '👥', text: 'Загрузка пользователей...', weight: 70 }, + { emoji: '📅', text: 'Обновление календаря...', weight: 75 }, + { emoji: '🔔', text: 'Настройка уведомлений...', weight: 80 }, + { emoji: '💾', text: 'Сохранение кэша...', weight: 85 }, + { emoji: '✨', text: 'Финальная обработка...', weight: 90 }, + { emoji: '🚀', text: 'Запуск приложения...', weight: 95 }, + { emoji: '✅', text: 'Почти готово...', weight: 98 } + ]; + + // Дополнительные случайные статусы + const RANDOM_STATUSES = [ + { emoji: '☕', text: 'Наливаем кофе...' }, + { emoji: '🧹', text: 'Подметаем баги...' }, + { emoji: '🎮', text: 'Играем в косынку...' }, + { emoji: '📚', text: 'Читаем документацию...' }, + { emoji: '🎵', text: 'Включаем музыку...' }, + { emoji: '🧘', text: 'Медитируем...' }, + { emoji: '🏋️', text: 'Качаем бицепс...' }, + { emoji: '🍕', text: 'Заказываем пиццу...' }, + { emoji: '🌴', text: 'Улетаем в отпуск...' }, + { emoji: '🎪', text: 'Устраиваем цирк...' }, + { emoji: '🎭', text: 'Играем спектакль...' }, + { emoji: '🎯', text: 'Целимся в баги...' }, + { emoji: '🎲', text: 'Бросаем кубик...' }, + { emoji: '🎰', text: 'Крутим барабан...' }, + { emoji: '🦄', text: 'Ловим единорога...' }, + { emoji: '🌈', text: 'Красим радугу...' }, + { emoji: '🪄', text: 'Колдуем...' }, + { emoji: '🎩', text: 'Достаем кролика...' }, + { emoji: '🕯️', text: 'Зажигаем свечи...' }, + { emoji: '🔮', text: 'Гадаем на баги...' } + ]; + + // Функция для инициализации после загрузки DOM + function initializeLoadingOverlay() { + // Проверяем, существует ли уже оверлей + if (document.getElementById('loading-overlay')) { + return; + } + + // Создаем контейнер для анимации загрузки + const loadingOverlay = document.createElement('div'); + loadingOverlay.id = 'loading-overlay'; + loadingOverlay.innerHTML = ` +
+
+
+
+
+

School CRM

+

Загрузка сервиса управления задачами...

+
+
+
+
+ 📦 Инициализация... +
+
0%
+
+
+ +
+ `; + + // Добавляем стили для анимации со случайными цветами + const styles = document.createElement('style'); + styles.textContent = ` + #loading-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: ${gradientColors.background}; + display: flex; + justify-content: center; + align-items: center; + z-index: 9999; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; + transition: opacity 0.5s ease-out; + } + + #loading-overlay.fade-out { + opacity: 0; + } + + .loading-container { + text-align: center; + color: ${textColor}; + max-width: 500px; + width: 90%; + padding: 40px; + background: rgba(255, 255, 255, 0.15); + border-radius: 20px; + backdrop-filter: blur(10px); + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + animation: slideUp 0.8s ease-out; + border: 1px solid rgba(255, 255, 255, 0.2); + } + + @keyframes slideUp { + from { + transform: translateY(50px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } + } + + .loading-spinner { + margin-bottom: 30px; + } + + .spinner { + width: 80px; + height: 80px; + margin: 0 auto; + border: 5px solid rgba(255, 255, 255, 0.2); + border-radius: 50%; + border-top-color: ${progressColor}; + border-left-color: ${accentColor}; + border-right-color: ${accentColor}; + animation: spin 1s ease-in-out infinite; + box-shadow: 0 0 20px ${progressColor}40; + } + + @keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } + } + + .loading-text h2 { + font-size: 32px; + margin: 0 0 15px; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2); + letter-spacing: 2px; + background: linear-gradient(135deg, ${textColor}, ${progressColor}); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + } + + .loading-text p { + font-size: 18px; + margin: 0 0 30px; + opacity: 0.9; + color: ${textColor}; + } + + .loading-progress { + width: 100%; + height: 12px; + background: rgba(255, 255, 255, 0.15); + border-radius: 6px; + margin: 20px 0; + overflow: hidden; + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2); + } + + .progress-bar { + width: 0%; + height: 100%; + background: linear-gradient(90deg, ${progressColor}, ${accentColor}, ${progressColor}); + border-radius: 6px; + transition: width 0.3s ease; + animation: progressPulse 2s ease-in-out infinite; + background-size: 200% 100%; + } + + @keyframes progressPulse { + 0%, 100% { opacity: 1; background-position: 0% 50%; } + 50% { opacity: 0.8; background-position: 100% 50%; } + } + + .loading-status { + font-size: 18px; + margin: 15px 0; + min-height: 28px; + font-weight: 500; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + color: ${textColor}; + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + + .loading-percent { + font-size: 42px; + font-weight: bold; + margin: 10px 0; + text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.3); + background: linear-gradient(135deg, ${textColor}, ${progressColor}); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + animation: percentPulse 1.5s ease-in-out infinite; + } + + @keyframes percentPulse { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.05); } + } + + .loading-details { + font-size: 13px; + margin-top: 15px; + opacity: 0.8; + min-height: 20px; + color: ${textColor}; + background: rgba(255, 255, 255, 0.1); + padding: 8px 15px; + border-radius: 20px; + display: inline-block; + } + + .loading-footer { + margin-top: 30px; + font-size: 14px; + opacity: 0.8; + color: ${textColor}; + } + + .loading-footer p { + margin: 5px 0; + color: ${textColor}; + } + + .loading-tip { + font-size: 13px; + font-style: italic; + color: ${textColor}; + animation: fadeInOut 3s ease-in-out infinite; + background: rgba(255, 255, 255, 0.1); + padding: 5px 15px; + border-radius: 20px; + margin-top: 10px; + } + + @keyframes fadeInOut { + 0%, 100% { opacity: 0.7; transform: translateY(0); } + 50% { opacity: 1; transform: translateY(-2px); } + } + + .status-icon { + display: inline-block; + animation: bounce 1s ease infinite; + filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2)); + } + + @keyframes bounce { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-5px); } + } + + .retry-button { + background: rgba(255, 255, 255, 0.2); + border: 2px solid ${textColor}; + color: ${textColor}; + padding: 12px 30px; + border-radius: 30px; + cursor: pointer; + font-size: 16px; + margin-top: 20px; + transition: all 0.3s ease; + font-weight: bold; + text-transform: uppercase; + letter-spacing: 1px; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); + } + + .retry-button:hover { + background: ${progressColor}; + color: ${gradientColors.start}; + transform: scale(1.05); + border-color: ${progressColor}; + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); + } + + .warning { + color: #ffd700; + text-shadow: 0 0 10px #ffd700; + } + + .error { + color: #ff6b6b; + text-shadow: 0 0 10px #ff6b6b; + } + + .progress-dots { + display: inline-block; + min-width: 30px; + } + + .progress-dots:after { + content: '...'; + animation: dots 1.5s steps(4, end) infinite; + font-weight: bold; + letter-spacing: 2px; + } + + @keyframes dots { + 0%, 20% { content: ''; } + 40% { content: '.'; } + 60% { content: '..'; } + 80%, 100% { content: '...'; } + } + + /* Добавляем случайные частицы для эффекта */ + .loading-container::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: radial-gradient(circle, ${progressColor}20 1px, transparent 1px); + background-size: 50px 50px; + animation: particles 20s linear infinite; + opacity: 0.1; + pointer-events: none; + } + + @keyframes particles { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } + } + `; + + // Добавляем элементы в DOM с проверкой + if (document.head) { + document.head.appendChild(styles); + } else { + document.addEventListener('DOMContentLoaded', () => { + document.head.appendChild(styles); + }); + } + + if (document.body) { + document.body.appendChild(loadingOverlay); + } else { + document.addEventListener('DOMContentLoaded', () => { + document.body.appendChild(loadingOverlay); + }); + } + + // Логируем использованные цвета + console.log('🎨 Случайные цвета загрузки:', { + gradient: gradientColors, + progress: progressColor, + accent: accentColor, + text: textColor + }); + } + + // Инициализируем оверлей после загрузки DOM + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initializeLoadingOverlay); + } else { + initializeLoadingOverlay(); + } + + // Массив полезных советов + const TIPS = [ + '💡 Совет: Используйте фильтры для быстрого поиска задач', + '💡 Совет: Можно прикреплять до 15 файлов к задаче', + '💡 Совет: Настраивайте уведомления в личном кабинете', + '💡 Совет: Используйте Канбан-доску для визуализации задач', + '💡 Совет: Отмечайте важные задачи цветными метками', + '💡 Совет: Следите за просроченными задачами в отдельной вкладке', + '💡 Совет: Создавайте шаблоны для частых типов задач', + '💡 Совет: Используйте поиск для быстрого доступа к задачам', + '💡 Совет: Комментируйте задачи для уточнения деталей', + '💡 Совет: Прикрепляйте скриншоты к задачам в ИТ-отдел', + '💡 Совет: Проверяйте уведомления на email ежедневно', + '💡 Совет: Архивируйте выполненные задачи для чистоты списка', + '🌟 Факт: Сегодняшний градиент - один из 30 возможных', + '🎨 Факт: Цвета загрузки уникальны для каждого обновления', + '🌈 Факт: У вас сегодня особенная цветовая схема' + ]; + + // Счетчик загруженных скриптов + window.loadingScripts = { + total: 0, + loaded: 0, + errors: [], + retryCounts: {}, + startTime: Date.now(), + scripts: [], + animationInterval: null, + tipInterval: null, + loadingComplete: false, + minTimeElapsed: false, + + updateProgress: function() { + const percent = Math.min(Math.round((this.loaded / this.total) * 100), 100); + const progressBar = document.getElementById('loading-progress-bar'); + const percentDisplay = document.getElementById('loading-percent'); + const statusDisplay = document.getElementById('loading-status'); + const detailsDisplay = document.getElementById('loading-details'); + + if (progressBar) progressBar.style.width = percent + '%'; + if (percentDisplay) percentDisplay.textContent = percent + '%'; + + // Обновляем статус в зависимости от процента загрузки + if (statusDisplay) { + if (this.errors.length > 0) { + statusDisplay.innerHTML = '⚠️ Проблемы с загрузкой. Пытаемся восстановить...'; + } else { + // Находим подходящий статус по проценту загрузки + const currentStatus = LOADING_STATUSES.find(s => s.weight >= percent) || LOADING_STATUSES[LOADING_STATUSES.length - 1]; + statusDisplay.innerHTML = `${currentStatus.emoji} ${currentStatus.text} `; + } + } + + // Обновляем детальную информацию + if (detailsDisplay) { + const loadedScripts = this.loaded; + const totalScripts = this.total; + const errors = this.errors.length; + const timeElapsed = ((Date.now() - this.startTime) / 1000).toFixed(1); + + let detailsHtml = `📊 ${loadedScripts}/${totalScripts} модулей • ⏱️ ${timeElapsed}с`; + + if (errors > 0) { + detailsHtml += `
⚠️ Ошибок: ${errors}`; + } + + detailsDisplay.innerHTML = detailsHtml; + } + + // Проверяем, все ли скрипты загружены + if (this.loaded >= this.total && this.total > 0 && !this.loadingComplete) { + this.loadingComplete = true; + this.checkAndFinishLoading(); + } + }, + + checkAndFinishLoading: function() { + const elapsed = Date.now() - this.startTime; + + // Если прошло минимум 5 секунд, завершаем загрузку + if (elapsed >= CONFIG.minLoadingTime) { + this.finishLoading(); + } else { + // Иначе ждем оставшееся время + const remainingTime = CONFIG.minLoadingTime - elapsed; + + // Показываем сообщение о финальной подготовке + const statusDisplay = document.getElementById('loading-status'); + if (statusDisplay) { + statusDisplay.innerHTML = ' Финальная подготовка интерфейса... '; + } + + setTimeout(() => { + this.finishLoading(); + }, remainingTime); + } + }, + + startDynamicLoading: function() { + // Запускаем анимацию прогресса + let fakePercent = 0; + const startTime = Date.now(); + + this.animationInterval = setInterval(() => { + const elapsed = Date.now() - startTime; + const realPercent = Math.min(Math.round((this.loaded / this.total) * 100), 100); + + // Рассчитываем фейковый процент на основе реального и времени + if (elapsed < CONFIG.maxLoadingTime && !this.loadingComplete) { + // Фейковый процент растет медленнее к концу + const timePercent = (elapsed / CONFIG.maxLoadingTime) * 100; + fakePercent = Math.min( + Math.max(realPercent, Math.floor(timePercent * 0.7 + Math.random() * 10)), + 99 // Никогда не достигаем 100% фейком + ); + } else { + // Если загрузка завершена или прошло 15 секунд, показываем реальный процент + fakePercent = realPercent; + } + + // Обновляем прогресс бар с фейковым процентом + const progressBar = document.getElementById('loading-progress-bar'); + const percentDisplay = document.getElementById('loading-percent'); + + if (progressBar) progressBar.style.width = fakePercent + '%'; + if (percentDisplay) percentDisplay.textContent = fakePercent + '%'; + + // Случайно меняем статус для динамики + if (Math.random() > 0.7 && !this.loadingComplete) { + const statusDisplay = document.getElementById('loading-status'); + if (statusDisplay && this.errors.length === 0) { + const randomStatus = RANDOM_STATUSES[Math.floor(Math.random() * RANDOM_STATUSES.length)]; + statusDisplay.innerHTML = `${randomStatus.emoji} ${randomStatus.text} `; + + // Возвращаем нормальный статус через 2 секунды + setTimeout(() => { + const currentStatus = LOADING_STATUSES.find(s => s.weight >= fakePercent) || LOADING_STATUSES[LOADING_STATUSES.length - 1]; + if (statusDisplay && !this.loadingComplete) { + statusDisplay.innerHTML = `${currentStatus.emoji} ${currentStatus.text} `; + } + }, 2000); + } + } + + // Проверяем, не пора ли закончить по времени + if (elapsed >= CONFIG.maxLoadingTime && !this.loadingComplete) { + this.loadingComplete = true; + this.finishDynamicLoading(); + } + }, 200); + + // Запускаем смену советов + this.tipInterval = setInterval(() => { + const tipElement = document.getElementById('loading-tip'); + if (tipElement) { + const randomTip = TIPS[Math.floor(Math.random() * TIPS.length)]; + tipElement.textContent = randomTip; + } + }, 3000); + }, + + finishDynamicLoading: function() { + if (this.animationInterval) { + clearInterval(this.animationInterval); + this.animationInterval = null; + } + + // Показываем реальный прогресс + const realPercent = Math.min(Math.round((this.loaded / this.total) * 100), 100); + const progressBar = document.getElementById('loading-progress-bar'); + const percentDisplay = document.getElementById('loading-percent'); + + if (progressBar) progressBar.style.width = realPercent + '%'; + if (percentDisplay) percentDisplay.textContent = realPercent + '%'; + + // Завершаем загрузку + this.finishLoading(); + }, + + finishLoading: function() { + // Останавливаем интервалы + if (this.animationInterval) { + clearInterval(this.animationInterval); + this.animationInterval = null; + } + + if (this.tipInterval) { + clearInterval(this.tipInterval); + this.tipInterval = null; + } + + const loadingTime = ((Date.now() - this.startTime) / 1000).toFixed(1); + const statusDisplay = document.getElementById('loading-status'); + const percentDisplay = document.getElementById('loading-percent'); + + if (this.errors.length > 0) { + if (statusDisplay) { + statusDisplay.innerHTML = `⚠️ Загружено с ошибками (${this.errors.length}) за ${loadingTime}с`; + } + + if (percentDisplay) { + percentDisplay.innerHTML = '⚠️'; + } + + // Показываем кнопку перезагрузки + const detailsDisplay = document.getElementById('loading-details'); + if (detailsDisplay) { + const retryButton = document.createElement('button'); + retryButton.className = 'retry-button'; + retryButton.innerHTML = '🔄 Перезагрузить страницу'; + retryButton.onclick = () => window.location.reload(); + detailsDisplay.appendChild(retryButton); + } + + // Автоматическая перезагрузка через 5 секунд + setTimeout(() => { + if (confirm('Некоторые компоненты не загрузились. Перезагрузить страницу?')) { + window.location.reload(); + } + }, 5000); + } else { + if (statusDisplay) { + statusDisplay.innerHTML = ` Загрузка завершена за ${loadingTime}с`; + } + + if (percentDisplay) { + percentDisplay.innerHTML = '100%'; + } + + // Плавно скрываем оверлей + setTimeout(() => { + const overlay = document.getElementById('loading-overlay'); + if (overlay) { + overlay.classList.add('fade-out'); + setTimeout(() => { + if (overlay && overlay.parentNode) { + overlay.parentNode.removeChild(overlay); + } + }, 500); + } + }, 800); + } + }, + + retryScript: function(scriptSrc) { + if (!this.retryCounts[scriptSrc]) { + this.retryCounts[scriptSrc] = 0; + } + + if (this.retryCounts[scriptSrc] >= CONFIG.maxRetries) { + console.error(`Не удалось загрузить скрипт после ${CONFIG.maxRetries} попыток: ${scriptSrc}`); + return false; + } + + this.retryCounts[scriptSrc]++; + this.loaded--; // Уменьшаем счетчик загруженных + + const statusDisplay = document.getElementById('loading-status'); + if (statusDisplay) { + statusDisplay.innerHTML = `🔄 Перезагрузка: ${scriptSrc.split('/').pop()} (попытка ${this.retryCounts[scriptSrc]})`; + } + + // Создаем новый script элемент + const script = document.createElement('script'); + script.src = scriptSrc; + script.async = false; + + script.onload = () => { + this.loaded++; + this.errors = this.errors.filter(e => e !== scriptSrc); + this.updateProgress(); + }; + + script.onerror = () => { + setTimeout(() => this.retryScript(scriptSrc), CONFIG.retryDelay); + }; + + document.head.appendChild(script); + return true; + } + }; + + // Перехватываем загрузку скриптов + const originalAppendChild = document.head ? document.head.appendChild : null; + + if (originalAppendChild) { + document.head.appendChild = function(node) { + if (node.tagName === 'SCRIPT' && node.src) { + window.loadingScripts.total++; + window.loadingScripts.scripts.push(node.src); + + // Устанавливаем таймаут для скрипта + const timeoutId = setTimeout(() => { + console.warn(`Таймаут загрузки скрипта: ${node.src}`); + if (window.loadingScripts.retryScript(node.src)) { + // Удаляем старый скрипт + if (node.parentNode) { + node.parentNode.removeChild(node); + } + } + }, CONFIG.timeout); + + // Обновляем обработчики событий + const originalOnLoad = node.onload; + node.onload = function() { + clearTimeout(timeoutId); + window.loadingScripts.loaded++; + window.loadingScripts.updateProgress(); + if (originalOnLoad) originalOnLoad.apply(this, arguments); + }; + + const originalOnError = node.onerror; + node.onerror = function() { + clearTimeout(timeoutId); + window.loadingScripts.errors.push(node.src); + window.loadingScripts.updateProgress(); + + // Пытаемся перезагрузить скрипт + if (window.loadingScripts.retryScript(node.src)) { + // Удаляем старый скрипт + if (node.parentNode) { + node.parentNode.removeChild(node); + } + } + + if (originalOnError) originalOnError.apply(this, arguments); + }; + } + return originalAppendChild.call(this, node); + }; + } + + // Добавляем обработчик для DOMContentLoaded + document.addEventListener('DOMContentLoaded', () => { + // Запускаем динамическую загрузку + window.loadingScripts.startDynamicLoading(); + + // Устанавливаем максимальное время загрузки 15 секунд + setTimeout(() => { + if (!window.loadingScripts.loadingComplete && window.loadingScripts.loaded < window.loadingScripts.total) { + window.loadingScripts.loadingComplete = true; + window.loadingScripts.finishDynamicLoading(); + } + }, CONFIG.maxLoadingTime); + + // Проверяем, не зависла ли загрузка + setTimeout(() => { + if (window.loadingScripts.loaded < window.loadingScripts.total) { + const unloadedScripts = window.loadingScripts.scripts.filter(src => + !window.loadingScripts.retryCounts[src] || + window.loadingScripts.retryCounts[src] < CONFIG.maxRetries + ); + + unloadedScripts.forEach(src => { + window.loadingScripts.retryScript(src); + }); + } + }, 5000); + }); +})(); \ No newline at end of file