/** * Template Name: iPortfolio - v3.9.1 * Template URL: https://bootstrapmade.com/iportfolio-bootstrap-portfolio-websites-template/ * Author: BootstrapMade.com * License: https://bootstrapmade.com/license/ */ (function() { "use strict"; // Track instances for cleanup to prevent memory leaks const instances = { swipers: [], isotope: null, lightbox: null, waypoints: [], aos: null, intervals: [], timeouts: [], listeners: [] }; /** * Easy selector helper function */ const select = (el, all = false) => { el = el.trim() if (all) { return [...document.querySelectorAll(el)] } else { return document.querySelector(el) } } /** * Easy event listener function with tracking for cleanup */ const on = (type, el, listener, all = false) => { let selectEl = select(el, all) if (selectEl) { if (all) { selectEl.forEach(e => { e.addEventListener(type, listener) instances.listeners.push({ element: e, type, listener }); }) } else { selectEl.addEventListener(type, listener) instances.listeners.push({ element: selectEl, type, listener }); } } } /** * Easy on scroll event listener with tracking */ const onscroll = (el, listener) => { el.addEventListener('scroll', listener) instances.listeners.push({ element: el, type: 'scroll', listener }); } /** * Cleanup function to prevent memory leaks on page unload */ function cleanup() { // Clear intervals instances.intervals.forEach(id => clearInterval(id)); instances.intervals = []; // Clear timeouts instances.timeouts.forEach(id => clearTimeout(id)); instances.timeouts = []; // Destroy Swiper instances instances.swipers.forEach(swiper => { if (swiper && typeof swiper.destroy === 'function') { try { swiper.destroy(true, true); } catch (e) { /* ignore */ } } }); instances.swipers = []; // Destroy Isotope if (instances.isotope && typeof instances.isotope.destroy === 'function') { try { instances.isotope.destroy(); } catch (e) { /* ignore */ } instances.isotope = null; } // Destroy GLightbox if (instances.lightbox && typeof instances.lightbox.destroy === 'function') { try { instances.lightbox.destroy(); } catch (e) { /* ignore */ } instances.lightbox = null; } // Destroy Waypoints instances.waypoints.forEach(wp => { if (wp && typeof wp.destroy === 'function') { try { wp.destroy(); } catch (e) { /* ignore */ } } }); instances.waypoints = []; // Remove tracked event listeners instances.listeners.forEach(({ element, type, listener }) => { try { element.removeEventListener(type, listener); } catch (e) { /* ignore */ } }); instances.listeners = []; } // Register cleanup on page unload window.addEventListener('beforeunload', cleanup); window.addEventListener('pagehide', cleanup); /** * Navbar links active state on scroll */ let navbarlinks = select('#navbar .scrollto', true) const navbarlinksActive = () => { let position = window.scrollY + 200 navbarlinks.forEach(navbarlink => { if (!navbarlink.hash) return let section = select(navbarlink.hash) if (!section) return if (position >= section.offsetTop && position <= (section.offsetTop + section.offsetHeight)) { navbarlink.classList.add('active') } else { navbarlink.classList.remove('active') } }) } window.addEventListener('load', navbarlinksActive) onscroll(document, navbarlinksActive) /** * Scrolls to an element with header offset */ const scrollto = (el) => { let elementPos = select(el).offsetTop window.scrollTo({ top: elementPos, behavior: 'smooth' }) } /** * Back to top button */ let backtotop = select('.back-to-top') if (backtotop) { const toggleBacktotop = () => { if (window.scrollY > 100) { backtotop.classList.add('active') } else { backtotop.classList.remove('active') } } window.addEventListener('load', toggleBacktotop) onscroll(document, toggleBacktotop) } /** * Mobile nav toggle - Enhanced to ensure it works correctly */ document.addEventListener('DOMContentLoaded', function() { // Mobile nav toggle const mobileNavToggle = document.querySelector('.mobile-nav-toggle'); if (mobileNavToggle) { mobileNavToggle.addEventListener('click', function(e) { e.preventDefault(); document.body.classList.toggle('mobile-nav-active'); this.classList.toggle('bi-list'); this.classList.toggle('bi-x'); console.log('Mobile nav toggled'); }); } // Close mobile nav when clicking outside document.addEventListener('click', function(e) { if (document.body.classList.contains('mobile-nav-active') && !e.target.closest('#navbar') && !e.target.closest('.mobile-nav-toggle')) { document.body.classList.remove('mobile-nav-active'); const toggle = document.querySelector('.mobile-nav-toggle'); if (toggle) { toggle.classList.add('bi-list'); toggle.classList.remove('bi-x'); } } }); }); /** * Scrool with ofset on links with a class name .scrollto */ on('click', '.scrollto', function(e) { if (select(this.hash)) { e.preventDefault() let body = select('body') if (body.classList.contains('mobile-nav-active')) { body.classList.remove('mobile-nav-active') let navbarToggle = select('.mobile-nav-toggle') navbarToggle.classList.toggle('bi-list') navbarToggle.classList.toggle('bi-x') } scrollto(this.hash) } }, true) /** * Scroll with ofset on page load with hash links in the url */ window.addEventListener('load', () => { if (window.location.hash) { if (select(window.location.hash)) { scrollto(window.location.hash) } } // Hide preloader when page is loaded const preloader = select('#preloader'); if (preloader) { setTimeout(() => { preloader.style.opacity = '0'; setTimeout(() => { preloader.style.display = 'none'; }, 500); }, 1000); } // Initialize PureCounter // new PureCounter(); // Removed duplicate initialization }); /** * Hero type effect */ const typed = select('.typed') if (typed) { let typed_strings = typed.getAttribute('data-typed-items') typed_strings = typed_strings.split(',') new Typed('.typed', { strings: typed_strings, loop: true, typeSpeed: 100, backSpeed: 50, backDelay: 2000 }); } /** * Skills animation */ let skilsContent = select('.skills-content'); if (skilsContent) { const waypoint = new Waypoint({ element: skilsContent, offset: '80%', handler: function(direction) { let progress = select('.progress .progress-bar', true); progress.forEach((el) => { el.style.width = el.getAttribute('aria-valuenow') + '%' }); } }) instances.waypoints.push(waypoint); } /** * Porfolio isotope and filter */ window.addEventListener('load', () => { let portfolioContainer = select('.portfolio-container'); if (portfolioContainer) { let portfolioIsotope = new Isotope(portfolioContainer, { itemSelector: '.portfolio-item' }); instances.isotope = portfolioIsotope; let portfolioFilters = select('#portfolio-flters li', true); on('click', '#portfolio-flters li', function(e) { e.preventDefault(); portfolioFilters.forEach(function(el) { el.classList.remove('filter-active'); }); this.classList.add('filter-active'); portfolioIsotope.arrange({ filter: this.getAttribute('data-filter') }); portfolioIsotope.on('arrangeComplete', function() { AOS.refresh() }); }, true); } }); /** * Initiate portfolio lightbox */ const portfolioLightbox = GLightbox({ selector: '.portfolio-lightbox' }); instances.lightbox = portfolioLightbox; /** * Portfolio details slider */ const portfolioSlider = new Swiper('.portfolio-details-slider', { speed: 400, loop: true, autoplay: { delay: 5000, disableOnInteraction: false }, pagination: { el: '.swiper-pagination', type: 'bullets', clickable: true } }); instances.swipers.push(portfolioSlider); /** * Testimonials slider */ const testimonialsSlider = new Swiper('.testimonials-slider', { speed: 600, loop: true, autoplay: { delay: 5000, disableOnInteraction: false }, slidesPerView: 'auto', pagination: { el: '.swiper-pagination', type: 'bullets', clickable: true }, breakpoints: { 320: { slidesPerView: 1, spaceBetween: 20 }, 1200: { slidesPerView: 3, spaceBetween: 20 } } }); instances.swipers.push(testimonialsSlider); /** * Animation on scroll */ window.addEventListener('load', () => { AOS.init({ duration: 1000, easing: 'ease-in-out', once: true, mirror: false }) }) /** * Initiate Pure Counter */ // new PureCounter(); // Removed duplicate initialization /** * Journey Flowchart Carousel - with cleanup for memory leaks */ function initJourneyFlowchart() { const carousel = document.querySelector('.flowchart-carousel'); const steps = Array.from(document.querySelectorAll('.journey-step')); const prevBtn = document.querySelector('.journey-prev'); const nextBtn = document.querySelector('.journey-next'); const dots = Array.from(document.querySelectorAll('.journey-dot')); if (!carousel || steps.length === 0) return; let currentIndex = 0; let autoplayInterval = null; let pauseTimeout = null; let isPaused = false; // Initialize the flowchart updateFlowchart(currentIndex); startAutoplay(); // Track event listeners for cleanup const clickHandlers = []; // Event listeners for navigation buttons if (prevBtn) { const prevHandler = () => { clearAutoplay(); if (currentIndex > 0) { currentIndex--; updateFlowchart(currentIndex); } }; prevBtn.addEventListener('click', prevHandler); clickHandlers.push({ element: prevBtn, type: 'click', handler: prevHandler }); } if (nextBtn) { const nextHandler = () => { clearAutoplay(); if (currentIndex < steps.length - 1) { currentIndex++; updateFlowchart(currentIndex); } }; nextBtn.addEventListener('click', nextHandler); clickHandlers.push({ element: nextBtn, type: 'click', handler: nextHandler }); } // Event listeners for dot indicators dots.forEach((dot, index) => { const dotHandler = () => { clearAutoplay(); currentIndex = index; updateFlowchart(currentIndex); }; dot.addEventListener('click', dotHandler); clickHandlers.push({ element: dot, type: 'click', handler: dotHandler }); }); // Hover events for steps steps.forEach((step, index) => { const enterHandler = () => { isPaused = true; clearTimeout(pauseTimeout); clearInterval(autoplayInterval); if (index !== currentIndex) { currentIndex = index; updateFlowchart(currentIndex); } }; const leaveHandler = () => { resetPauseTimeout(); }; step.addEventListener('mouseenter', enterHandler); step.addEventListener('mouseleave', leaveHandler); clickHandlers.push( { element: step, type: 'mouseenter', handler: enterHandler }, { element: step, type: 'mouseleave', handler: leaveHandler } ); }); // Start autoplay function function startAutoplay() { clearInterval(autoplayInterval); autoplayInterval = setInterval(() => { if (!isPaused) { currentIndex = (currentIndex + 1) % steps.length; updateFlowchart(currentIndex); } }, 5000); } // Clear autoplay function clearAutoplay() { clearInterval(autoplayInterval); isPaused = true; resetPauseTimeout(); } // Reset pause timeout function resetPauseTimeout() { clearTimeout(pauseTimeout); pauseTimeout = setTimeout(() => { isPaused = false; startAutoplay(); }, 10000); } // Update flowchart display function updateFlowchart(index) { // Update button states if (prevBtn) prevBtn.disabled = index === 0; if (nextBtn) nextBtn.disabled = index === steps.length - 1; // Update dots if (dots.length > 0) { dots.forEach((dot, i) => { dot.classList.toggle('active', i === index); }); } // Update steps steps.forEach((step, i) => { // Remove all classes first step.classList.remove('active', 'prev', 'next', 'far-prev', 'far-next'); // Calculate relative position const position = i - index; // Add appropriate class based on position if (position === 0) { step.classList.add('active'); step.style.transform = 'translateX(0) scale(1.1)'; step.style.opacity = '1'; step.style.zIndex = '10'; } else if (position === -1) { step.classList.add('prev'); step.style.transform = 'translateX(-120%) scale(0.9)'; step.style.opacity = '0.7'; step.style.zIndex = '5'; } else if (position === 1) { step.classList.add('next'); step.style.transform = 'translateX(120%) scale(0.9)'; step.style.opacity = '0.7'; step.style.zIndex = '5'; } else if (position < -1) { step.classList.add('far-prev'); step.style.transform = 'translateX(-200%) scale(0.7)'; step.style.opacity = '0'; step.style.zIndex = '1'; } else if (position > 1) { step.classList.add('far-next'); step.style.transform = 'translateX(200%) scale(0.7)'; step.style.opacity = '0'; step.style.zIndex = '1'; } }); } // Cleanup function registered with global cleanup function destroy() { clearInterval(autoplayInterval); clearTimeout(pauseTimeout); clickHandlers.forEach(({ element, type, handler }) => { try { element.removeEventListener(type, handler); } catch (e) { /* ignore */ } }); } // Track intervals and timeouts for cleanup instances.intervals.push(autoplayInterval); instances.timeouts.push(pauseTimeout); return { destroy }; } // Initialize when DOM is loaded document.addEventListener('DOMContentLoaded', function() { initJourneyFlowchart(); }); })();