Продолжение: Дополнительные утилиты

13. Прелоадер (Preloader)

HTML структура:

<!-- Прелоадер -->
<div class="preloader" id="preloader">
    <div class="preloader-content">
        <div class="preloader-logo">
            <svg viewBox="0 0 100 100" class="logo-svg">
                <circle cx="50" cy="50" r="45" class="logo-circle"/>
                <path d="M30,50 L45,65 L70,35" class="logo-check"/>
            </svg>
        </div>
        <div class="preloader-spinner">
            <div class="spinner-ring"></div>
            <div class="spinner-ring"></div>
            <div class="spinner-ring"></div>
        </div>
        <p class="preloader-text">Загрузка<span class="dots"></span></p>
        <div class="preloader-progress">
            <div class="progress-bar-preloader">
                <div class="progress-fill-preloader" id="preloaderProgress"></div>
            </div>
            <span class="progress-percent" id="preloaderPercent">0%</span>
        </div>
    </div>
</div>

CSS стили для прелоадера:

/* Прелоадер */
.preloader {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 99999;
    transition: opacity 0.5s ease, visibility 0.5s ease;
}

.preloader.hidden {
    opacity: 0;
    visibility: hidden;
}

.preloader-content {
    text-align: center;
    max-width: 300px;
}

/* Логотип */
.preloader-logo {
    width: 100px;
    height: 100px;
    margin: 0 auto 30px;
}

.logo-svg {
    width: 100%;
    height: 100%;
}

.logo-circle {
    fill: none;
    stroke: var(--primary-color);
    stroke-width: 3;
    stroke-dasharray: 283;
    stroke-dashoffset: 283;
    animation: drawCircle 2s ease-in-out infinite;
}

.logo-check {
    fill: none;
    stroke: var(--primary-color);
    stroke-width: 4;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-dasharray: 60;
    stroke-dashoffset: 60;
    animation: drawCheck 2s ease-in-out infinite;
    animation-delay: 0.5s;
}

/* Спиннер */
.preloader-spinner {
    position: relative;
    width: 80px;
    height: 80px;
    margin: 0 auto 20px;
}

.spinner-ring {
    position: absolute;
    width: 100%;
    height: 100%;
    border: 3px solid transparent;
    border-top-color: var(--primary-color);
    border-radius: 50%;
    animation: spin 1.5s cubic-bezier(0.68, -0.55, 0.265, 1.55) infinite;
}

.spinner-ring:nth-child(2) {
    width: 70%;
    height: 70%;
    top: 15%;
    left: 15%;
    border-top-color: var(--secondary-color);
    animation-delay: 0.2s;
}

.spinner-ring:nth-child(3) {
    width: 40%;
    height: 40%;
    top: 30%;
    left: 30%;
    border-top-color: #ffa94d;
    animation-delay: 0.4s;
}

/* Текст загрузки */
.preloader-text {
    font-size: 18px;
    font-weight: 600;
    color: var(--text-color);
    margin-bottom: 20px;
}

.dots::after {
    content: '';
    animation: dots 1.5s steps(4, end) infinite;
}

/* Прогресс-бар */
.preloader-progress {
    width: 100%;
}

.progress-bar-preloader {
    width: 100%;
    height: 4px;
    background: #e9ecef;
    border-radius: 2px;
    overflow: hidden;
    margin-bottom: 10px;
}

.progress-fill-preloader {
    height: 100%;
    background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
    width: 0%;
    transition: width 0.3s ease;
    border-radius: 2px;
}

.progress-percent {
    font-size: 14px;
    font-weight: 600;
    color: var(--primary-color);
}

/* Анимации */
@keyframes drawCircle {
    0% {
        stroke-dashoffset: 283;
    }
    50% {
        stroke-dashoffset: 0;
    }
    100% {
        stroke-dashoffset: 283;
    }
}

@keyframes drawCheck {
    0% {
        stroke-dashoffset: 60;
    }
    50% {
        stroke-dashoffset: 0;
    }
    100% {
        stroke-dashoffset: 60;
    }
}

@keyframes spin {
    0% {
        transform: rotate(0deg);
    }
    100% {
        transform: rotate(360deg);
    }
}

@keyframes dots {
    0%, 20% {
        content: '';
    }
    40% {
        content: '.';
    }
    60% {
        content: '..';
    }
    80%, 100% {
        content: '...';
    }
}

14. Система уведомлений (Toast Notifications)

HTML структура:

<!-- Контейнер для уведомлений -->
<div class="toast-container" id="toastContainer"></div>

CSS стили для уведомлений:

/* Контейнер уведомлений */
.toast-container {
    position: fixed;
    top: 20px;
    right: 20px;
    z-index: 99998;
    display: flex;
    flex-direction: column;
    gap: 15px;
    max-width: 400px;
}

/* Уведомление */
.toast {
    background: white;
    border-radius: 12px;
    padding: 16px 20px;
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
    display: flex;
    align-items: flex-start;
    gap: 15px;
    min-width: 300px;
    animation: slideInRight 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
    position: relative;
    overflow: hidden;
}

.toast.removing {
    animation: slideOutRight 0.3s ease forwards;
}

/* Иконка уведомления */
.toast-icon {
    width: 24px;
    height: 24px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 18px;
}

/* Контент уведомления */
.toast-content {
    flex: 1;
}

.toast-title {
    font-size: 15px;
    font-weight: 600;
    color: var(--text-color);
    margin-bottom: 4px;
}

.toast-message {
    font-size: 14px;
    color: #6c757d;
    line-height: 1.5;
    margin: 0;
}

/* Кнопка закрытия */
.toast-close {
    width: 24px;
    height: 24px;
    flex-shrink: 0;
    background: transparent;
    border: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #6c757d;
    transition: all 0.3s ease;
    border-radius: 6px;
    padding: 0;
}

.toast-close:hover {
    background: #f8f9fa;
    color: var(--text-color);
}

/* Прогресс-бар */
.toast-progress {
    position: absolute;
    bottom: 0;
    left: 0;
    height: 3px;
    background: currentColor;
    width: 100%;
    transform-origin: left;
    animation: progressBar linear forwards;
}

/* Типы уведомлений */
.toast.success {
    border-left: 4px solid #4CAF50;
}

.toast.success .toast-icon {
    color: #4CAF50;
}

.toast.success .toast-progress {
    color: #4CAF50;
}

.toast.error {
    border-left: 4px solid #f44336;
}

.toast.error .toast-icon {
    color: #f44336;
}

.toast.error .toast-progress {
    color: #f44336;
}

.toast.warning {
    border-left: 4px solid #ff9800;
}

.toast.warning .toast-icon {
    color: #ff9800;
}

.toast.warning .toast-progress {
    color: #ff9800;
}

.toast.info {
    border-left: 4px solid #2196F3;
}

.toast.info .toast-icon {
    color: #2196F3;
}

.toast.info .toast-progress {
    color: #2196F3;
}

/* Анимации */
@keyframes slideInRight {
    from {
        transform: translateX(400px);
        opacity: 0;
    }
    to {
        transform: translateX(0);
        opacity: 1;
    }
}

@keyframes slideOutRight {
    from {
        transform: translateX(0);
        opacity: 1;
    }
    to {
        transform: translateX(400px);
        opacity: 0;
    }
}

@keyframes progressBar {
    from {
        transform: scaleX(1);
    }
    to {
        transform: scaleX(0);
    }
}

/* Адаптивность */
@media (max-width: 768px) {
    .toast-container {
        top: auto;
        bottom: 20px;
        right: 20px;
        left: 20px;
        max-width: none;
    }
    
    .toast {
        min-width: auto;
        width: 100%;
    }
}

15. Плавная прокрутка и навигация

HTML структура:

<!-- Кнопка "Наверх" -->
<button class="scroll-to-top" id="scrollToTop" aria-label="Прокрутить наверх">
    <i class="fas fa-arrow-up"></i>
</button>

<!-- Индикатор прогресса чтения -->
<div class="reading-progress" id="readingProgress">
    <div class="reading-progress-bar"></div>
</div>

<!-- Быстрая навигация -->
<nav class="quick-nav" id="quickNav">
    <div class="quick-nav-header">
        <span>Навигация</span>
        <button class="quick-nav-toggle" id="quickNavToggle">
            <i class="fas fa-chevron-down"></i>
        </button>
    </div>
    <ul class="quick-nav-list">
        <li><a href="#services" data-scroll>Услуги</a></li>
        <li><a href="#portfolio" data-scroll>Портфолио</a></li>
        <li><a href="#process" data-scroll>Процесс работы</a></li>
        <li><a href="#reviews" data-scroll>Отзывы</a></li>
        <li><a href="#faq" data-scroll>Вопросы</a></li>
        <li><a href="#contacts" data-scroll>Контакты</a></li>
    </ul>
</nav>

CSS стили:

/* Кнопка "Наверх" */
.scroll-to-top {
    position: fixed;
    bottom: 30px;
    right: 30px;
    width: 50px;
    height: 50px;
    background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
    border: none;
    border-radius: 50%;
    color: white;
    font-size: 20px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 5px 20px rgba(255, 107, 107, 0.3);
    z-index: 9998;
    opacity: 0;
    visibility: hidden;
    transform: translateY(20px);
    transition: all 0.3s ease;
}

.scroll-to-top.visible {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
}

.scroll-to-top:hover {
    transform: translateY(-5px);
    box-shadow: 0 8px 25px rgba(255, 107, 107, 0.4);
}

.scroll-to-top:active {
    transform: translateY(-2px);
}

/* Индикатор прогресса чтения */
.reading-progress {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 3px;
    background: rgba(0, 0, 0, 0.05);
    z-index: 9999;
    opacity: 0;
    transition: opacity 0.3s ease;
}

.reading-progress.visible {
    opacity: 1;
}

.reading-progress-bar {
    height: 100%;
    background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
    width: 0%;
    transition: width 0.1s ease;
}

/* Быстрая навигация */
.quick-nav {
    position: fixed;
    left: 30px;
    top: 50%;
    transform: translateY(-50%);
    background: white;
    border-radius: 15px;
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
    padding: 15px;
    z-index: 9997;
    opacity: 0;
    visibility: hidden;
    transition: all 0.3s ease;
}

.quick-nav.visible {
    opacity: 1;
    visibility: visible;
}

.quick-nav.collapsed .quick-nav-list {
    max-height: 0;
    opacity: 0;
    margin-top: 0;
}

.quick-nav-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding-bottom: 10px;
    border-bottom: 1px solid #e9ecef;
    margin-bottom: 10px;
}

.quick-nav-header span {
    font-size: 14px;
    font-weight: 600;
    color: var(--text-color);
}

.quick-nav-toggle {
    width: 24px;
    height: 24px;
    background: transparent;
    border: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #6c757d;
    transition: all 0.3s ease;
    border-radius: 6px;
}

.quick-nav-toggle:hover {
    background: #f8f9fa;
}

.quick-nav.collapsed .quick-nav-toggle i {
    transform: rotate(-180deg);
}

.quick-nav-list {
    list-style: none;
    padding: 0;
    margin: 0;
    max-height: 300px;
    opacity: 1;
    overflow: hidden;
    transition: all 0.3s ease;
}

.quick-nav-list li {
    margin-bottom: 5px;
}

.quick-nav-list li:last-child {
    margin-bottom: 0;
}

.quick-nav-list a {
    display: block;
    padding: 8px 12px;
    color: #6c757d;
    text-decoration: none;
    font-size: 14px;
    border-radius: 8px;
    transition: all 0.3s ease;
    position: relative;
}

.quick-nav-list a::before {
    content: '';
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    width: 3px;
    height: 0;
    background: var(--primary-color);
    border-radius: 0 2px 2px 0;
    transition: height 0.3s ease;
}

.quick-nav-list a:hover,
.quick-nav-list a.active {
    color: var(--primary-color);
    background: rgba(255, 107, 107, 0.05);
    padding-left: 18px;
}

.quick-nav-list a:hover::before,
.quick-nav-list a.active::before {
    height: 100%;
}

/* Адаптивность */
@media (max-width: 1200px) {
    .quick-nav {
        left: 20px;
    }
}

@media (max-width: 992px) {
    .quick-nav {
        display: none;
    }
    
    .scroll-to-top {
        bottom: 20px;
        right: 20px;
        width: 45px;
        height: 45px;
        font-size: 18px;
    }
}

@media (max-width: 576px) {
    .scroll-to-top {
        bottom: 15px;
        right: 15px;
        width: 40px;
        height: 40px;
        font-size: 16px;
    }
}

16. JavaScript для всех утилит

JavaScript код:

// ============================================
// ПРЕЛОАДЕР
// ============================================
class Preloader {
    constructor() {
        this.preloader = document.getElementById('preloader');
        this.progressBar = document.getElementById('preloaderProgress');
        this.progressPercent = document.getElementById('preloaderPercent');
        this.progress = 0;
        this.isLoaded = false;
        
        if (this.preloader) {
            this.init();
        }
    }
    
    init() {
        // Симуляция загрузки
        this.simulateLoading();
        
        // Реальная загрузка ресурсов
        this.trackRealLoading();
        
        // Минимальное время показа прелоадера
        setTimeout(() => {
            this.isLoaded = true;
            this.checkComplete();
        }, 1000);
    }
    
    simulateLoading() {
        const interval = setInterval(() => {
            if (this.progress < 90) {
                this.progress += Math.random() * 10;
                this.updateProgress(Math.min(this.progress, 90));
            } else {
                clearInterval(interval);
            }
        }, 200);
    }
    
    trackRealLoading() {
        if (document.readyState === 'complete') {
            this.onLoadComplete();
        } else {
            window.addEventListener('load', () => this.onLoadComplete());
        }
    }
    
    onLoadComplete() {
        this.updateProgress(100);
        setTimeout(() => {
            this.isLoaded = true;
            this.checkComplete();
        }, 300);
    }
    
    checkComplete() {
        if (this.isLoaded && this.progress >= 100) {
            this.hide();
        }
    }
    
    updateProgress(value) {
        this.progress = value;
        if (this.progressBar) {
            this.progressBar.style.width = value + '%';
        }
        if (this.progressPercent) {
            this.progressPercent.textContent = Math.round(value) + '%';
        }
        
        if (value >= 100) {
            this.checkComplete();
        }
    }
    
    hide() {
        this.preloader.classList.add('hidden');
        document.body.style.overflow = '';
        
        setTimeout(() => {
            this.preloader.style.display = 'none';
        }, 500);
    }
}

// ============================================
// СИСТЕМА УВЕДОМЛЕНИЙ
// ============================================
class ToastNotification {
    constructor() {
        this.container = document.getElementById('toastContainer');
        if (!this.container) {
            this.createContainer();
        }
        this.toasts = [];
    }
    
    createContainer() {
        this.container = document.createElement('div');
        this.container.id = 'toastContainer';
        this.container.className = 'toast-container';
        document.body.appendChild(this.container);
    }
    
    show(options = {}) {
        const {
            type = 'info',
            title = '',
            message = '',
            duration = 5000,
            closable = true
        } = options;
        
        const toast = this.createToast(type, title, message, closable);
        this.container.appendChild(toast);
        this.toasts.push(toast);
        
        // Автоматическое удаление
        if (duration > 0) {
            const progressBar = toast.querySelector('.toast-progress');
            if (progressBar) {
                progressBar.style.animationDuration = duration + 'ms';
            }
            
            setTimeout(() => {
                this.remove(toast);
            }, duration);
        }
        
        return toast;
    }
    
    createToast(type, title, message, closable) {
        const toast = document.createElement('div');
        toast.className = `toast ${type}`;
        
        const icons = {
            success: 'fa-check-circle',
            error: 'fa-times-circle',
            warning: 'fa-exclamation-triangle',
            info: 'fa-info-circle'
        };
        
        toast.innerHTML = `
            <div class="toast-icon">
                <i class="fas ${icons[type] || icons.info}"></i>
            </div>
            <div class="toast-content">
                ${title ? `<div class="toast-title">${title}</div>` : ''}
                ${message ? `<p class="toast-message">${message}</p>` : ''}
            </div>
            ${closable ? `
                <button class="toast-close" aria-label="Закрыть">
                    <i class="fas fa-times"></i>
                </button>
            ` : ''}
            <div class="toast-progress"></div>
        `;
        
        // Обработчик закрытия
        if (closable) {
            const closeBtn = toast.querySelector('.toast-close');
            closeBtn.addEventListener('click', () => this.remove(toast));
        }
        
        return toast;
    }
    
    remove(toast) {
        toast.classList.add('removing');
        
        setTimeout(() => {
            if (toast.parentNode) {
                toast.parentNode.removeChild(toast);
            }
            this.toasts = this.toasts.filter(t => t !== toast);
        }, 300);
    }
    
    // Удобные методы
    success(title, message, duration) {
        return this.show({ type: 'success', title, message, duration });
    }
    
    error(title, message, duration) {
        return this.show({ type: 'error', title, message, duration });
    }
    
    warning(title, message, duration) {
        return this.show({ type: 'warning', title, message, duration });
    }
    
    info(title, message, duration) {
        return this.show({ type: 'info', title, message, duration });
    }
    
    clearAll() {
        this.toasts.forEach(toast => this.remove(toast));
    }
}

// Глобальный экземпляр
window.toast = new ToastNotification();

// ============================================
// ПЛАВНАЯ ПРОКРУТКА
// ============================================
class SmoothScroll {
    constructor() {
        this.init();
    }
    
    init() {
        // Обработка всех ссылок с data-scroll
        document.querySelectorAll('[data-scroll]').forEach(link => {
            link.addEventListener('click', (e) => {
                e.preventDefault();
                const targetId = link.getAttribute('href');
                this.scrollTo(targetId);
            });
        });
        
        // Обработка якорных ссылок
        document.querySelectorAll('a[href^="#"]').forEach(link => {
            if (!link.hasAttribute('data-scroll')) {
                link.addEventListener('click', (e) => {
                    const targetId = link.getAttribute('href');
                    if (targetId !== '#' && document.querySelector(targetId)) {
                        e.preventDefault();
                        this.scrollTo(targetId);
                    }
                });
            }
        });
    }
    
    scrollTo(target, offset = 80) {
        const element = typeof target === 'string' 
            ? document.querySelector(target) 
            : target;
            
        if (!element) return;
        
        const targetPosition = element.getBoundingClientRect().top + window.pageYOffset;
        const startPosition = window.pageYOffset;
        const distance = targetPosition - startPosition - offset;
        const duration = 1000;
        let start = null;
        
        const animation = (currentTime) => {
            if (start === null) start = currentTime;
            const timeElapsed = currentTime - start;
            const run = this.ease(timeElapsed, startPosition, distance, duration);
            window.scrollTo(0, run);
            
            if (timeElapsed < duration) {
                requestAnimationFrame(animation);
            }
        };
        
        requestAnimationFrame(animation);
    }
    
    ease(t, b, c, d) {
        t /= d / 2;
        if (t < 1) return c / 2 * t * t + b;
        t--;
        return -c / 2 * (t * (t - 2) - 1) + b;
    }
}

// ============================================
// КНОПКА "НАВЕРХ"
// ============================================
class ScrollToTop {
    constructor() {
        this.button = document.getElementById('scrollToTop');
        if (this.button) {
            this.init();
        }
    }
    
    init() {
        // Показ/скрытие кнопки при прокрутке
        window.addEventListener('scroll', () => {
            if (window.pageYOffset > 300) {
                this.button.classList.add('visible');
            } else {
                this.button.classList.remove('visible');
            }
        });
        
        // Клик по кнопке
        this.button.addEventListener('click', () => {
            window.scrollTo({
                top: 0,
                behavior: 'smooth'
            });
        });
    }
}

// ============================================
// ИНДИКАТОР ПРОГРЕССА ЧТЕНИЯ
// ============================================
class ReadingProgress {
    constructor() {
        this.indicator = document.getElementById('readingProgress');
        if (this.indicator) {
            this.bar = this.indicator.querySelector('.reading-progress-bar');
            this.init();
        }
    }
    
    init() {
        window.addEventListener('scroll', () => {
            this.updateProgress();
        });
        
        this.updateProgress();
    }
    
    updateProgress() {
        const windowHeight = window.innerHeight;
        const documentHeight = document.documentElement.scrollHeight - windowHeight;
        const scrolled = window.pageYOffset;
        const progress = (scrolled / documentHeight) * 100;
        
        if (this.bar) {
            this.bar.style.width = progress + '%';
        }
        
        // Показываем индикатор только при прокрутке
        if (scrolled > 100) {
            this.indicator.classList.add('visible');
        } else {
            this.indicator.classList.remove('visible');
        }
    }
}

// ============================================
// БЫСТРАЯ НАВИГАЦИЯ
// ============================================
class QuickNavigation {
    constructor() {
        this.nav = document.getElementById('quickNav');
        if (this.nav) {
            this.toggle = document.getElementById('quickNavToggle');
            this.links = this.nav.querySelectorAll('.quick-nav-list a');
            this.init();
        }
    }
    
    init() {
        // Показ/скрытие навигации
        window.addEventListener('scroll', () => {
            if (window.pageYOffset > 500) {
                this.nav.classList.add('visible');
            } else {
                this.nav.classList.remove('visible');
            }
            
            this.updateActiveLink();
        });
        
        // Сворачивание/разворачивание
        this.toggle?.addEventListener('click', () => {
            this.nav.classList.toggle('collapsed');
        });
        
        // Обновление активной ссылки при загрузке
        this.updateActiveLink();
    }
    
    updateActiveLink() {
        let currentSection = '';
        
        this.links.forEach(link => {
            const section = document.querySelector(link.getAttribute('href'));
            if (section) {
                const sectionTop = section.offsetTop;
                const sectionHeight = section.clientHeight;
                
                if (window.pageYOffset >= sectionTop - 200) {
                    currentSection = link.getAttribute('href');
                }
            }
        });
        
        this.links.forEach(link => {
            link.classList.remove('active');
            if (link.getAttribute('href') === currentSection) {
                link.classList.add('active');
            }
        });
    }
}

// ============================================
// ЛЕНИВАЯ ЗАГРУЗКА ИЗОБРАЖЕНИЙ
// ============================================
class LazyLoad {
    constructor() {
        this.images = document.querySelectorAll('img[data-src], img[loading="lazy"]');
        this.init();
    }
    
    init() {
        if ('IntersectionObserver' in window) {
            this.observer = new IntersectionObserver((entries) => {
                entries.forEach(entry => {
                    if (entry.isIntersecting) {
                        this.loadImage(entry.target);
                    }
                });
            }, {
                rootMargin: '50px'
            });
            
            this.images.forEach(img => this.observer.observe(img));
        } else {
            // Fallback для старых браузеров
            this.images.forEach(img => this.loadImage(img));
        }
    }
    
    loadImage(img) {
        const src = img.dataset.src || img.src;
        if (!src) return;
        
        img.src = src;
        img.classList.add('loaded');
        
        if (this.observer) {
            this.observer.unobserve(img);
        }
    }
}

// ============================================
// АНИМАЦИЯ ПРИ ПОЯВЛЕНИИ
// ============================================
class ScrollReveal {
    constructor() {
        this.elements = document.querySelectorAll('[data-reveal]');
        this.init();
    }
    
    init() {
        if ('IntersectionObserver' in window) {
            this.observer = new IntersectionObserver((entries) => {
                entries.forEach(entry => {
                    if (entry.isIntersecting) {
                        entry.target.classList.add('revealed');
                        this.observer.unobserve(entry.target);
                    }
                });
            }, {
                threshold: 0.15
            });
            
            this.elements.forEach(el => {
                el.classList.add('reveal');
                this.observer.observe(el);
            });
        } else {
            // Fallback
            this.elements.forEach(el => el.classList.add('revealed'));
        }
    }
}

// CSS для анимации появления (добавить в styles.css)
/*
.reveal {
    opacity: 0;
    transform: translateY(30px);
    transition: all 0.6s ease;
}

.reveal.revealed {
    opacity: 1;
    transform: translateY(0);
}
*/

// ============================================
// ПАРАЛЛАКС ЭФФЕКТ
// ============================================
class Parallax {
    constructor() {
        this.elements = document.querySelectorAll('[data-parallax]');
        if (this.elements.length > 0) {
            this.init();
        }
    }
    
    init() {
        window.addEventListener('scroll', () => {
            this.update();
        });
        
        this.update();
    }
    
    update() {
        const scrolled = window.pageYOffset;
        
        this.elements.forEach(element => {
            const speed = parseFloat(element.dataset.parallax) || 0.5;
            const yPos = -(scrolled * speed);
            element.style.transform = `translateY(${yPos}px)`;
        });
    }
}

// ============================================
// КОПИРОВАНИЕ В БУФЕР ОБМЕНА
// ============================================
class ClipboardCopy {
    constructor() {
        this.buttons = document.querySelectorAll('[data-clipboard]');
        this.init();
    }
    
    init() {
        this.buttons.forEach(button => {
            button.addEventListener('click', () => {
                const text = button.dataset.clipboard;
                this.copy(text, button);
            });
        });
    }
    
    async copy(text, button) {
        try {
            await navigator.clipboard.writeText(text);
            
            // Визуальная обратная связь
            const originalText = button.innerHTML;
            button.innerHTML = '<i class="fas fa-check"></i> Скопировано!';
            button.classList.add('copied');
            
            setTimeout(() => {
                button.innerHTML = originalText;
                button.classList.remove('copied');
            }, 2000);
            
            // Уведомление
            if (window.toast) {
                window.toast.success('Скопировано', 'Текст скопирован в буфер обмена');
            }
        } catch (err) {
            console.error('Ошибка копирования:', err);
            if (window.toast) {
                window.toast.error('Ошибка', 'Не удалось скопировать текст');
            }
        }
    }
}

// ============================================
// ИНИЦИАЛИЗАЦИЯ ВСЕХ УТИЛИТ
// ============================================
document.addEventListener('DOMContentLoaded', () => {
    // Прелоадер
    new Preloader();
    
    // Плавная прокрутка
    new SmoothScroll();
    
    // Кнопка "Наверх"
    new ScrollToTop();
    
    // Индикатор прогресса
    new ReadingProgress();
    
    // Быстрая навигация
    new QuickNavigation();
    
    // Ленивая загрузка
    new LazyLoad();
    
    // Анимация появления
    new ScrollReveal();
    
    // Параллакс
    new Parallax();
    
    // Копирование в буфер
    new ClipboardCopy();
});

// ============================================
// ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
// ============================================

// Дебаунс
function debounce(func, wait) {
    let timeout;
    return function executedFunction(...args) {
        const later = () => {
            clearTimeout(timeout);
            func(...args);
        };
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
    };
}

// Троттлинг
function throttle(func, limit) {
    let inThrottle;
    return function(...args) {
        if (!inThrottle) {
            func.apply(this, args);
            inThrottle = true;
            setTimeout(() => inThrottle = false, limit);
        }
    };
}

// Проверка видимости элемента
function isElementInViewport(el) {
    const rect = el.getBoundingClientRect();
    return (
        rect.top >= 0 &&
        rect.left >= 0 &&
        rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
        rect.right <= (window.innerWidth || document.documentElement.clientWidth)
    );
}

// Получение параметров URL
function getUrlParams() {
    const params = {};
    const queryString = window.location.search.substring(1);
    const pairs = queryString.split('&');
    
    pairs.forEach(pair => {
        const [key, value] = pair.split('=');
        if (key) {
            params[decodeURIComponent(key)] = decodeURIComponent(value || '');
        }
    });
    
    return params;
}

// Форматирование числа
function formatNumber(num) {
    return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
}

// Форматирование телефона
function formatPhone(phone) {
    const cleaned = phone.replace(/\D/g, '');
    const match = cleaned.match(/^(\d{1})(\d{3})(\d{3})(\d{2})(\d{2})$/);
    
    if (match) {
        return `+${match[1]} (${match[2]}) ${match[3]}-${match[4]}-${match[5]}`;
    }
    
    return phone;
}

// Валидация email
function isValidEmail(email) {
    const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return re.test(email);
}

// Валидация телефона
function isValidPhone(phone) {
    const cleaned = phone.replace(/\D/g, '');
    return cleaned.length === 11;
}

// Получение cookie
function getCookie(name) {
    const value = `; ${document.cookie}`;
    const parts = value.split(`; ${name}=`);
    if (parts.length === 2) return parts.pop().split(';').shift();
}

// Установка cookie
function setCookie(name, value, days = 365) {
    const date = new Date();
    date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
    const expires = `expires=${date.toUTCString()}`;
    document.cookie = `${name}=${value};${expires};path=/`;
}

// Удаление cookie
function deleteCookie(name) {
    document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/;`;
}

// Генерация случайного ID
function generateId(length = 8) {
    return Math.random().toString(36).substring(2, length + 2);
}

// Задержка (Promise)
function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

// Экспорт функций для глобального использования
window.utils = {
    debounce,
    throttle,
    isElementInViewport,
    getUrlParams,
    formatNumber,
    formatPhone,
    isValidEmail,
    isValidPhone,
    getCookie,
    setCookie,
    deleteCookie,
    generateId,
    delay
};