PageSourceSearch

https://ignishun.com/script.js

js ignishun.com collected 2026-09-28 12:48:19 UTC 3,790 bytes, 98 lines download raw bytes

1document.addEventListener('DOMContentLoaded', () => {
2  const navbar = document.getElementById('navbar');
3  const navToggle = document.getElementById('navToggle');
4  const primaryNav = document.getElementById('primaryNav');
5  const navLinks = document.querySelectorAll('.nav-link');
6  const backToTop = document.getElementById('backToTop');
7  const sections = document.querySelectorAll('main section[id]');
8  const contactForm = document.getElementById('contactForm');
9  const formNote = document.getElementById('formNote');
10  const yearEl = document.getElementById('year');
11
12  if (yearEl) yearEl.textContent = new Date().getFullYear();
13
14  // Navbar background + back-to-top visibility on scroll
15  const onScroll = () => {
16    const scrolled = window.scrollY > 20;
17    navbar.classList.toggle('scrolled', scrolled);
18    backToTop.classList.toggle('show', window.scrollY > 500);
19  };
20  onScroll();
21  window.addEventListener('scroll', onScroll, { passive: true });
22
23  // Mobile menu toggle
24  navToggle.addEventListener('click', () => {
25    const isOpen = primaryNav.classList.toggle('open');
26    navToggle.classList.toggle('open', isOpen);
27    navToggle.setAttribute('aria-expanded', String(isOpen));
28  });
29
30  // Close mobile menu after tapping a link
31  navLinks.forEach((link) => {
32    link.addEventListener('click', () => {
33      primaryNav.classList.remove('open');
34      navToggle.classList.remove('open');
35      navToggle.setAttribute('aria-expanded', 'false');
36    });
37  });
38
39  // Back to top: animated manually so it scrolls smoothly across browsers instead of
40  // relying on native smooth-scroll, which some browsers silently skip.
41  function smoothScrollToTop(duration = 600) {
42    const startY = window.scrollY;
43    if (startY === 0) return;
44    if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
45      window.scrollTo(0, 0);
46      return;
47    }
48    const start = performance.now();
49    function step(now) {
50      const progress = Math.min((now - start) / duration, 1);
51      const eased = 1 - Math.pow(1 - progress, 3);
52      // behavior: 'auto' forces an instant jump per frame; the global CSS
53      // scroll-behavior: smooth would otherwise turn each frame into its own
54      // native smooth-scroll animation, fighting this manual easing.
55      window.scrollTo({ top: startY * (1 - eased), left: 0, behavior: 'auto' });
56      if (progress < 1) requestAnimationFrame(step);
57    }
58    requestAnimationFrame(step);
59  }
60
61  backToTop.addEventListener('click', () => smoothScrollToTop());
62
63  // Highlight active nav link based on section in view
64  const observer = new IntersectionObserver(
65    (entries) => {
66      entries.forEach((entry) => {
67        if (entry.isIntersecting) {
68          const id = entry.target.getAttribute('id');
69          navLinks.forEach((link) => {
70            link.classList.toggle('active', link.getAttribute('href') === `#${id}`);
71          });
72        }
73      });
74    },
75    { rootMargin: '-45% 0px -50% 0px', threshold: 0 }
76  );
77  sections.forEach((section) => observer.observe(section));
78
79  // Submits to Netlify Forms via AJAX so the page doesn't reload; requires the site to be deployed on Netlify.
80  if (contactForm) {
81    contactForm.addEventListener('submit', (e) => {
82      e.preventDefault();
83      const body = new URLSearchParams(new FormData(contactForm)).toString();
84      fetch('/', {
85        method: 'POST',
86        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
87        body,
88      })
89        .then(() => {
90          formNote.textContent = "Thanks! We'll get back to you within one business day.";
91          contactForm.reset();
92        })
93        .catch(() => {
94          formNote.textContent = 'Something went wrong. Please email us at [email protected] instead.';
95        });
96    });
97  }
98});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.