Spaces:
Running
Running
Delete script.js
Browse files
script.js
DELETED
|
@@ -1,143 +0,0 @@
|
|
| 1 |
-
/* =========================================================
|
| 2 |
-
# File: script.js
|
| 3 |
-
# Adds: theme toggle, mobile menu, hash routing, scroll spy,
|
| 4 |
-
# slider, filters, lightbox, form validation.
|
| 5 |
-
# ========================================================= */
|
| 6 |
-
|
| 7 |
-
(function () {
|
| 8 |
-
const $ = (sel, root = document) => root.querySelector(sel);
|
| 9 |
-
const $$ = (sel, root = document) => [...root.querySelectorAll(sel)];
|
| 10 |
-
|
| 11 |
-
// Theme
|
| 12 |
-
$('#themeToggle')?.addEventListener('click', () => {
|
| 13 |
-
const root = document.documentElement;
|
| 14 |
-
const willDark = !root.classList.contains('dark');
|
| 15 |
-
root.classList.toggle('dark', willDark);
|
| 16 |
-
localStorage.setItem('nnai-theme', willDark ? 'dark' : 'light');
|
| 17 |
-
});
|
| 18 |
-
|
| 19 |
-
// Mobile menu
|
| 20 |
-
const menuBtn = $('#menuBtn');
|
| 21 |
-
const mobileMenu = $('#mobileMenu');
|
| 22 |
-
menuBtn?.addEventListener('click', () => {
|
| 23 |
-
const open = !mobileMenu.classList.contains('hidden');
|
| 24 |
-
mobileMenu.classList.toggle('hidden', open);
|
| 25 |
-
menuBtn.setAttribute('aria-expanded', String(!open));
|
| 26 |
-
});
|
| 27 |
-
mobileMenu?.addEventListener('click', (e) => {
|
| 28 |
-
const link = e.target.closest('[data-route]');
|
| 29 |
-
if (link) {
|
| 30 |
-
mobileMenu.classList.add('hidden');
|
| 31 |
-
menuBtn.setAttribute('aria-expanded', 'false');
|
| 32 |
-
}
|
| 33 |
-
});
|
| 34 |
-
|
| 35 |
-
// Routing
|
| 36 |
-
function goTo(hash) {
|
| 37 |
-
const id = (hash || '#home').replace('#','');
|
| 38 |
-
const el = document.getElementById(id);
|
| 39 |
-
if (!el) return;
|
| 40 |
-
if (location.hash !== '#' + id) history.pushState({ id }, '', '#' + id);
|
| 41 |
-
el.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
| 42 |
-
}
|
| 43 |
-
document.addEventListener('click', (e) => {
|
| 44 |
-
const a = e.target.closest('a[data-route]');
|
| 45 |
-
if (!a) return;
|
| 46 |
-
const href = a.getAttribute('href');
|
| 47 |
-
if (href?.startsWith('#')) {
|
| 48 |
-
e.preventDefault(); goTo(href);
|
| 49 |
-
}
|
| 50 |
-
});
|
| 51 |
-
window.addEventListener('popstate', (e) => {
|
| 52 |
-
const id = (e.state && e.state.id) || (location.hash.replace('#','') || 'home');
|
| 53 |
-
document.getElementById(id)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
| 54 |
-
});
|
| 55 |
-
window.addEventListener('load', () => goTo(location.hash || '#home'));
|
| 56 |
-
|
| 57 |
-
// Scroll spy
|
| 58 |
-
const sections = $$('.section');
|
| 59 |
-
const navLinks = $$('.nav-link');
|
| 60 |
-
const spy = new IntersectionObserver((entries) => {
|
| 61 |
-
entries.forEach((entry) => {
|
| 62 |
-
const id = entry.target.id;
|
| 63 |
-
const link = navLinks.find((a) => a.getAttribute('href') === '#' + id);
|
| 64 |
-
if (!link) return;
|
| 65 |
-
if (entry.isIntersecting) { navLinks.forEach(l => l.classList.remove('active')); link.classList.add('active'); }
|
| 66 |
-
});
|
| 67 |
-
}, { rootMargin: '-40% 0px -55% 0px', threshold: 0.01 });
|
| 68 |
-
sections.forEach((s) => spy.observe(s));
|
| 69 |
-
|
| 70 |
-
// Slider (simple, accessible)
|
| 71 |
-
const slides = $$('.slide');
|
| 72 |
-
let idx = slides.findIndex(s => s.classList.contains('is-active'));
|
| 73 |
-
if (idx < 0) idx = 0, slides[0]?.classList.add('is-active');
|
| 74 |
-
function show(i) {
|
| 75 |
-
if (!slides.length) return;
|
| 76 |
-
slides[idx]?.classList.remove('is-active');
|
| 77 |
-
idx = (i + slides.length) % slides.length;
|
| 78 |
-
slides[idx]?.classList.add('is-active');
|
| 79 |
-
}
|
| 80 |
-
$('.slider-btn.prev')?.addEventListener('click', () => show(idx - 1));
|
| 81 |
-
$('.slider-btn.next')?.addEventListener('click', () => show(idx + 1));
|
| 82 |
-
let auto = setInterval(() => show(idx + 1), 6000);
|
| 83 |
-
// Pause on hover/focus
|
| 84 |
-
const slider = $('.slider');
|
| 85 |
-
slider?.addEventListener('mouseenter', () => clearInterval(auto));
|
| 86 |
-
slider?.addEventListener('mouseleave', () => auto = setInterval(() => show(idx + 1), 6000));
|
| 87 |
-
|
| 88 |
-
// Portfolio filter
|
| 89 |
-
const grid = $('#portfolioGrid');
|
| 90 |
-
const filterButtons = $$('.filter-chip');
|
| 91 |
-
filterButtons.forEach((btn) => {
|
| 92 |
-
btn.addEventListener('click', () => {
|
| 93 |
-
filterButtons.forEach((b) => b.classList.remove('active'));
|
| 94 |
-
btn.classList.add('active');
|
| 95 |
-
const f = btn.dataset.filter;
|
| 96 |
-
$$('.portfolio-card', grid).forEach((card) => {
|
| 97 |
-
card.style.display = (f === '*' || card.dataset.cat === f) ? '' : 'none';
|
| 98 |
-
});
|
| 99 |
-
});
|
| 100 |
-
});
|
| 101 |
-
|
| 102 |
-
// Lightbox
|
| 103 |
-
const dlg = $('#lightbox');
|
| 104 |
-
const dlgImg = $('#lightboxImg');
|
| 105 |
-
const dlgClose = $('#lightboxClose');
|
| 106 |
-
function openLightbox(url) {
|
| 107 |
-
dlgImg.src = url;
|
| 108 |
-
if (typeof dlg.showModal === 'function') dlg.showModal();
|
| 109 |
-
else dlg.setAttribute('open','true');
|
| 110 |
-
}
|
| 111 |
-
function closeLightbox() {
|
| 112 |
-
dlgImg.src = '';
|
| 113 |
-
if (typeof dlg.close === 'function') dlg.close();
|
| 114 |
-
else dlg.removeAttribute('open');
|
| 115 |
-
}
|
| 116 |
-
document.addEventListener('click', (e) => {
|
| 117 |
-
const card = e.target.closest('[data-lightbox]');
|
| 118 |
-
if (!card) return;
|
| 119 |
-
e.preventDefault(); openLightbox(card.dataset.lightbox);
|
| 120 |
-
});
|
| 121 |
-
dlgClose?.addEventListener('click', closeLightbox);
|
| 122 |
-
dlg?.addEventListener('click', (e) => {
|
| 123 |
-
const rect = dlgImg.getBoundingClientRect();
|
| 124 |
-
const inside = e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom;
|
| 125 |
-
if (!inside) closeLightbox();
|
| 126 |
-
});
|
| 127 |
-
document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && dlg?.open) closeLightbox(); });
|
| 128 |
-
|
| 129 |
-
// Contact form validation
|
| 130 |
-
const form = $('#contactForm');
|
| 131 |
-
const formMsg = $('#formMsg');
|
| 132 |
-
form?.addEventListener('submit', (e) => {
|
| 133 |
-
if (!form.checkValidity()) {
|
| 134 |
-
e.preventDefault();
|
| 135 |
-
formMsg.textContent = '❗ Please complete all required fields correctly.';
|
| 136 |
-
formMsg.className = 'text-sm text-red-600 dark:text-red-400';
|
| 137 |
-
return;
|
| 138 |
-
}
|
| 139 |
-
formMsg.textContent = '✔️ Sending...';
|
| 140 |
-
formMsg.className = 'text-sm text-slate-600 dark:text-slate-300';
|
| 141 |
-
});
|
| 142 |
-
|
| 143 |
-
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|