// 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); }); })();