/** * ArcaneNeko Website - Main Initialization * * Handles: mobile menu, scroll reveal, stat counters, * back-to-top, scroll progress, smooth scroll */ // ---------------------- // Mobile Hamburger Menu // ---------------------- function initMobileMenu() { const hamburger = document.getElementById('hamburger'); const mobileMenu = document.getElementById('mobileMenu'); if (!hamburger || !mobileMenu) return; hamburger.addEventListener('click', () => { const open = hamburger.classList.toggle('open'); mobileMenu.classList.toggle('open', open); hamburger.setAttribute('aria-expanded', String(open)); }); mobileMenu.querySelectorAll('a').forEach(link => { link.addEventListener('click', () => { hamburger.classList.remove('open'); mobileMenu.classList.remove('open'); hamburger.setAttribute('aria-expanded', 'false'); }); }); } // ---------------------- // Scroll Reveal (cards) // ---------------------- function initScrollReveal() { const observer = new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('visible'); observer.unobserve(entry.target); } }); }, { threshold: 0.1 }); document.querySelectorAll('.reveal').forEach(el => observer.observe(el)); } // ---------------------- // Stat Counter Animation // ---------------------- function animateCount(el, target, suffix, duration) { const start = performance.now(); const from = 0; function step(now) { const elapsed = now - start; const progress = Math.min(elapsed / duration, 1); const eased = 1 - Math.pow(1 - progress, 3); const current = Math.round(from + (target - from) * eased); el.textContent = current + suffix; if (progress < 1) requestAnimationFrame(step); } requestAnimationFrame(step); } function initStatCounters() { const statsBar = document.querySelector('.stats-bar'); if (!statsBar) return; const observer = new IntersectionObserver(entries => { if (!entries[0].isIntersecting) return; observer.disconnect(); document.querySelectorAll('.stat').forEach(el => el.classList.add('animated')); document.querySelectorAll('.stat-value[data-count]').forEach(el => { const target = parseFloat(el.dataset.count); const suffix = el.dataset.suffix || ''; const duration = 1400; animateCount(el, target, suffix, duration); }); }, { threshold: 0.5 }); observer.observe(statsBar); } // ---------------------- // Back to Top Button // ---------------------- function initBackToTop() { const btn = document.getElementById('backToTop'); if (!btn) return; window.addEventListener('scroll', () => { btn.classList.toggle('visible', window.scrollY > 450); }, { passive: true }); btn.addEventListener('click', () => { window.scrollTo({ top: 0, behavior: 'smooth' }); }); } // ---------------------- // Scroll Progress Bar (legal pages) // ---------------------- function initScrollProgress() { const bar = document.getElementById('scrollProgress'); if (!bar) return; const updateProgress = () => { const total = document.documentElement.scrollHeight - window.innerHeight; const progress = total > 0 ? (window.scrollY / total) * 100 : 0; const rounded = Math.round(progress); bar.style.width = progress + '%'; bar.setAttribute('aria-valuenow', String(rounded)); }; updateProgress(); window.addEventListener('scroll', updateProgress, { passive: true }); window.addEventListener('resize', updateProgress); } // ---------------------- // Navbar scroll shadow // ---------------------- function initNavShadow() { const navbar = document.querySelector('.navbar'); if (!navbar) return; window.addEventListener('scroll', () => { navbar.style.boxShadow = window.scrollY > 10 ? '0 4px 24px rgba(0,0,0,0.3)' : 'none'; }, { passive: true }); } // ---------------------- // Smooth scroll (hash links) // ---------------------- function initSmoothScroll() { document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', e => { const href = anchor.getAttribute('href'); if (href === '#') return; const target = document.querySelector(href); if (target) { e.preventDefault(); const offset = 80; window.scrollTo({ top: target.getBoundingClientRect().top + window.scrollY - offset, behavior: 'smooth' }); } }); }); } // ---------------------- // Legal page TOC highlight // ---------------------- function initTocHighlight() { const sections = document.querySelectorAll('.legal-section'); const tocLinks = document.querySelectorAll('.legal-toc a'); if (!sections.length || !tocLinks.length) return; const observer = new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.isIntersecting) { tocLinks.forEach(l => l.classList.remove('active')); const active = document.querySelector(`.legal-toc a[href="#${entry.target.id}"]`); if (active) active.classList.add('active'); } }); }, { rootMargin: '-20% 0px -70% 0px' }); sections.forEach(s => observer.observe(s)); } // ---------------------- // Legal page mobile TOC // ---------------------- function initLegalTocToggle() { document.querySelectorAll('.legal-toc').forEach(toc => { const button = toc.querySelector('.legal-toc-toggle'); const panel = toc.querySelector('.legal-toc-panel'); if (!button || !panel) return; button.addEventListener('click', () => { const open = toc.classList.toggle('open'); button.setAttribute('aria-expanded', String(open)); }); panel.querySelectorAll('a').forEach(link => { link.addEventListener('click', () => { if (window.innerWidth <= 768) { toc.classList.remove('open'); button.setAttribute('aria-expanded', 'false'); } }); }); }); } // ---------------------- // Init on DOM ready // ---------------------- document.addEventListener('DOMContentLoaded', () => { // Apply saved theme before anything renders if (window.ThemeUtils) { window.ThemeUtils.applyTheme(window.ThemeUtils.getCurrentTheme()); } // Theme toggle document.querySelectorAll('.theme-toggle').forEach(btn => { btn.addEventListener('click', () => { if (window.ThemeUtils) { window.ThemeUtils.applyTheme(window.ThemeUtils.nextTheme()); } }); }); initMobileMenu(); initScrollReveal(); initStatCounters(); initBackToTop(); initScrollProgress(); initNavShadow(); initSmoothScroll(); initTocHighlight(); initLegalTocToggle(); });