// ======================================== // 공통 JavaScript 모듈 // ======================================== // DOM 로드 완료 후 실행 document.addEventListener('DOMContentLoaded', function() { showPageLoading(); initializeNavigation(); setActiveNavLink(); loadComponents(); initLazyLoading(); hidePageLoading(); }); // ======================================== // 컴포넌트 로드 함수 // ======================================== async function loadComponents() { // Header 로드 const headerPlaceholder = document.getElementById('header-placeholder'); if (headerPlaceholder) { try { const response = await fetch('components/header.html'); const html = await response.text(); headerPlaceholder.innerHTML = html; initializeNavigation(); // 헤더 로드 후 네비게이션 초기화 } catch (error) { console.error('Error loading header:', error); } } // Footer 로드 const footerPlaceholder = document.getElementById('footer-placeholder'); if (footerPlaceholder) { try { const response = await fetch('components/footer.html'); const html = await response.text(); footerPlaceholder.innerHTML = html; } catch (error) { console.error('Error loading footer:', error); } } } // ======================================== // 네비게이션 기능 // ======================================== function initializeNavigation() { const hamburger = document.querySelector('.hamburger'); const navMenu = document.querySelector('.nav-menu'); if (hamburger && navMenu) { hamburger.addEventListener('click', function() { hamburger.classList.toggle('active'); navMenu.classList.toggle('active'); }); // 네비게이션 링크 클릭 시 모바일 메뉴 닫기 document.querySelectorAll('.nav-link').forEach(link => { link.addEventListener('click', () => { hamburger.classList.remove('active'); navMenu.classList.remove('active'); }); }); } // 스크롤 시 네비게이션 바 스타일 변경 let lastScroll = 0; window.addEventListener('scroll', () => { const navbar = document.querySelector('.navbar'); const currentScroll = window.pageYOffset; if (navbar) { if (currentScroll > 100) { navbar.style.boxShadow = '0 2px 20px rgba(0, 0, 0, 0.1)'; } else { navbar.style.boxShadow = '0 2px 10px rgba(0, 0, 0, 0.05)'; } } lastScroll = currentScroll; }); } // ======================================== // 현재 페이지 네비게이션 링크 활성화 // ======================================== function setActiveNavLink() { const currentPath = window.location.pathname; const navLinks = document.querySelectorAll('.nav-link'); navLinks.forEach(link => { const linkPath = link.getAttribute('href'); if (currentPath === linkPath || (currentPath === '/' && linkPath === '/') || (currentPath.includes(linkPath) && linkPath !== '/')) { link.classList.add('active'); } else { link.classList.remove('active'); } }); } // ======================================== // 스크롤 애니메이션 // ======================================== function initScrollAnimations() { const observerOptions = { threshold: 0.1, rootMargin: '0px 0px -100px 0px' }; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('animate-in'); } }); }, observerOptions); // 애니메이션 대상 요소 관찰 document.querySelectorAll('.animate-on-scroll').forEach(el => { observer.observe(el); }); } // ======================================== // 유틸리티 함수 // ======================================== // 이메일 유효성 검사 function isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } // 알림 표시 function showNotification(message, type = 'info') { // 기존 알림 제거 const existingNotification = document.querySelector('.notification'); if (existingNotification) { existingNotification.remove(); } // 새 알림 생성 const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.textContent = message; // 스타일 적용 notification.style.cssText = ` position: fixed; top: 80px; right: 20px; padding: 1rem 2rem; border-radius: 10px; color: white; font-weight: 600; z-index: 10000; transform: translateX(400px); transition: transform 0.3s ease; `; // 타입별 배경색 const bgColors = { success: '#10b981', error: '#ef4444', warning: '#f59e0b', info: '#3b82f6' }; notification.style.backgroundColor = bgColors[type] || bgColors.info; document.body.appendChild(notification); // 애니메이션 setTimeout(() => { notification.style.transform = 'translateX(0)'; }, 100); // 자동 제거 setTimeout(() => { notification.style.transform = 'translateX(400px)'; setTimeout(() => { if (notification.parentNode) { notification.remove(); } }, 300); }, 3000); } // 디바운스 함수 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); } }; } // ======================================== // Lazy Loading for YouTube iframes // ======================================== function initLazyLoading() { // YouTube iframe lazy loading const lazyIframes = document.querySelectorAll('iframe[data-src]'); if ('IntersectionObserver' in window) { const iframeObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const iframe = entry.target; iframe.src = iframe.dataset.src; iframe.removeAttribute('data-src'); iframeObserver.unobserve(iframe); } }); }, { rootMargin: '50px' }); lazyIframes.forEach(iframe => { iframeObserver.observe(iframe); }); } else { // Fallback for older browsers lazyIframes.forEach(iframe => { iframe.src = iframe.dataset.src; iframe.removeAttribute('data-src'); }); } // Image lazy loading (if any) const lazyImages = document.querySelectorAll('img[data-src]'); if ('IntersectionObserver' in window && lazyImages.length > 0) { const imageObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; img.classList.remove('lazy'); imageObserver.unobserve(img); } }); }, { rootMargin: '50px' }); lazyImages.forEach(img => { imageObserver.observe(img); }); } } // ======================================== // Export 함수들 (다른 스크립트에서 사용 가능) // ======================================== // ======================================== // 로딩 상태 관리 // ======================================== function showPageLoading() { // 페이지 로딩 오버레이가 이미 있는지 확인 if (document.getElementById('pageLoadingOverlay')) return; const loadingOverlay = document.createElement('div'); loadingOverlay.id = 'pageLoadingOverlay'; loadingOverlay.className = 'loading-overlay'; loadingOverlay.innerHTML = `