/* ========================================================= # File: script.js # Adds: theme toggle, mobile menu, hash routing, scroll spy, # slider, filters, lightbox, form validation. # ========================================================= */ (function () { const $ = (sel, root = document) => root.querySelector(sel); const $$ = (sel, root = document) => [...root.querySelectorAll(sel)]; // Theme $('#themeToggle')?.addEventListener('click', () => { const root = document.documentElement; const willDark = !root.classList.contains('dark'); root.classList.toggle('dark', willDark); localStorage.setItem('nnai-theme', willDark ? 'dark' : 'light'); }); // Mobile menu const menuBtn = $('#menuBtn'); const mobileMenu = $('#mobileMenu'); menuBtn?.addEventListener('click', () => { const open = !mobileMenu.classList.contains('hidden'); mobileMenu.classList.toggle('hidden', open); menuBtn.setAttribute('aria-expanded', String(!open)); }); mobileMenu?.addEventListener('click', (e) => { const link = e.target.closest('[data-route]'); if (link) { mobileMenu.classList.add('hidden'); menuBtn.setAttribute('aria-expanded', 'false'); } }); // Routing function goTo(hash) { const id = (hash || '#home').replace('#',''); const el = document.getElementById(id); if (!el) return; if (location.hash !== '#' + id) history.pushState({ id }, '', '#' + id); el.scrollIntoView({ behavior: 'smooth', block: 'start' }); } document.addEventListener('click', (e) => { const a = e.target.closest('a[data-route]'); if (!a) return; const href = a.getAttribute('href'); if (href?.startsWith('#')) { e.preventDefault(); goTo(href); } }); window.addEventListener('popstate', (e) => { const id = (e.state && e.state.id) || (location.hash.replace('#','') || 'home'); document.getElementById(id)?.scrollIntoView({ behavior: 'smooth', block: 'start' }); }); window.addEventListener('load', () => goTo(location.hash || '#home')); // Scroll spy const sections = $$('.section'); const navLinks = $$('.nav-link'); const spy = new IntersectionObserver((entries) => { entries.forEach((entry) => { const id = entry.target.id; const link = navLinks.find((a) => a.getAttribute('href') === '#' + id); if (!link) return; if (entry.isIntersecting) { navLinks.forEach(l => l.classList.remove('active')); link.classList.add('active'); } }); }, { rootMargin: '-40% 0px -55% 0px', threshold: 0.01 }); sections.forEach((s) => spy.observe(s)); // Slider (simple, accessible) const slides = $$('.slide'); let idx = slides.findIndex(s => s.classList.contains('is-active')); if (idx < 0) idx = 0, slides[0]?.classList.add('is-active'); function show(i) { if (!slides.length) return; slides[idx]?.classList.remove('is-active'); idx = (i + slides.length) % slides.length; slides[idx]?.classList.add('is-active'); } $('.slider-btn.prev')?.addEventListener('click', () => show(idx - 1)); $('.slider-btn.next')?.addEventListener('click', () => show(idx + 1)); let auto = setInterval(() => show(idx + 1), 6000); // Pause on hover/focus const slider = $('.slider'); slider?.addEventListener('mouseenter', () => clearInterval(auto)); slider?.addEventListener('mouseleave', () => auto = setInterval(() => show(idx + 1), 6000)); // Portfolio filter const grid = $('#portfolioGrid'); const filterButtons = $$('.filter-chip'); filterButtons.forEach((btn) => { btn.addEventListener('click', () => { filterButtons.forEach((b) => b.classList.remove('active')); btn.classList.add('active'); const f = btn.dataset.filter; $$('.portfolio-card', grid).forEach((card) => { card.style.display = (f === '*' || card.dataset.cat === f) ? '' : 'none'; }); }); }); // Lightbox const dlg = $('#lightbox'); const dlgImg = $('#lightboxImg'); const dlgClose = $('#lightboxClose'); function openLightbox(url) { dlgImg.src = url; if (typeof dlg.showModal === 'function') dlg.showModal(); else dlg.setAttribute('open','true'); } function closeLightbox() { dlgImg.src = ''; if (typeof dlg.close === 'function') dlg.close(); else dlg.removeAttribute('open'); } document.addEventListener('click', (e) => { const card = e.target.closest('[data-lightbox]'); if (!card) return; e.preventDefault(); openLightbox(card.dataset.lightbox); }); dlgClose?.addEventListener('click', closeLightbox); dlg?.addEventListener('click', (e) => { const rect = dlgImg.getBoundingClientRect(); const inside = e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom; if (!inside) closeLightbox(); }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && dlg?.open) closeLightbox(); }); // Contact form validation const form = $('#contactForm'); const formMsg = $('#formMsg'); form?.addEventListener('submit', (e) => { if (!form.checkValidity()) { e.preventDefault(); formMsg.textContent = '❗ Please complete all required fields correctly.'; formMsg.className = 'text-sm text-red-600 dark:text-red-400'; return; } formMsg.textContent = '✔️ Sending...'; formMsg.className = 'text-sm text-slate-600 dark:text-slate-300'; }); })();