gemini-ui-redesign / script.js
oldmonk69's picture
Update script.js
5fa412d verified
raw
history blame
5.42 kB
/* =========================================================
# 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';
});
})();