File size: 5,418 Bytes
5fa412d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/* =========================================================
# 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';
  });

})();