1/* 2* ---------------------------------------------------------------------------------------- 3Author : Tanvir Hossain 4Template Name: Wize - Creative Personal Portfolio 5Version : 1.0 6* ---------------------------------------------------------------------------------------- 7*/ 8 9 10(function($) { 11 12 "use strict"; 13 14 $(document).ready(function() { 15 16 /* 17 * ---------------------------------------------------------------------------------------- 18 * EXTRA JS - Close mobile menu on link click 19 * ---------------------------------------------------------------------------------------- 20 */ 21 // ÐÑполÑзÑем делегиÑование ÑобÑÑий на ÑлÑÑай динамиÑеÑкого Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ ÑÑÑлок 22 $('.main-header .navigation').on('click', '.nav-link-click', function(e) { 23 var $this = $(this); 24 var $parentLi = $this.closest('li'); 25 var $navbarCollapse = $('.navbar-collapse'); // ÐÐ°Ñ Ð¾Ð´Ð¸Ð¼ акÑÑалÑнÑй ÑÐ»ÐµÐ¼ÐµÐ½Ñ 26 27 // ÐакÑÑваем Ð¼ÐµÐ½Ñ ÑолÑко еÑли ÑÑо ÐÐ ÑодиÑелÑÑкий пÑÐ½ÐºÑ Ñ href="#" ÐÐРеÑли ÑÑо обÑÑÐ½Ð°Ñ ÑÑÑлка 28 if (!$parentLi.hasClass('has-children') || ($this.attr('href') && $this.attr('href') !== '#')) { 29 // ÐÑовеÑÑем, видимо ли Ð¼ÐµÐ½Ñ (оÑобенно на мобилÑнÑÑ ) 30 if ($('body').hasClass('mobile-menu-visible') || $navbarCollapse.is(':visible')) { 31 // УбиÑаем клаÑÑ Ñ body 32 $('body').removeClass('mobile-menu-visible'); 33 // УбиÑаем клаÑÑ active Ñ ÐºÐ½Ð¾Ð¿ÐºÐ¸-бÑÑгеÑа 34 $('.mobile-menu-toggle').removeClass('active'); 35 // СкÑÑваем Ñам блок Ð¼ÐµÐ½Ñ Ñ Ð°Ð½Ð¸Ð¼Ð°Ñией (надежнее) 36 if ($navbarCollapse.length > 0 && $(window).innerWidth() < 992) { 37 $navbarCollapse.slideUp(250); // Ðлавно ÑкÑÑÑÑ 38 } 39 40 // ÐакÑÑваем ÐСРоÑкÑÑÑÑе Ð¿Ð¾Ð´Ð¼ÐµÐ½Ñ Ð¿Ñи закÑÑÑии оÑновного 41 $('.main-header .navigation li.has-children.submenu-open') 42 .removeClass('submenu-open') 43 .children('ul.sub-menu').slideUp(200); // СкÑÑваем Ð¿Ð¾Ð´Ð¼ÐµÐ½Ñ 44 // УбиÑаем повоÑÐ¾Ñ Ñ ÐСÐÐ¥ иконок 45 $('.main-header .navigation li.has-children .dropdown-btn i').removeClass('rotate-180'); 46 } 47 } 48 // Ðажно: ÐÑли ÑÑо ÑÑÑлка href="#" внÑÑÑи has-children, Ñо клик по ней обÑабоÑаеÑÑÑ Ð½Ð¸Ð¶Ðµ 49 // и не должен закÑÑваÑÑ Ð¼ÐµÐ½Ñ ÑÑÐ°Ð·Ñ Ð¶Ðµ. ÐоÑÑÐ¾Ð¼Ñ Ð·Ð´ÐµÑÑ Ð½ÐµÑ else. 50 }); 51 52 53 /* 54 * ---------------------------------------------------------------------------------------- 55 * HEADER STYLE JS (Fix on scroll) 56 * ---------------------------------------------------------------------------------------- 57 */ 58 function headerStyle() { 59 if ($('.main-header').length) { 60 var windowpos = $(window).scrollTop(); 61 var siteHeader = $('.main-header'); 62 var scrollAmount = 100; // ÐоÑог ÑикÑаÑии Ñ ÐµÐ´ÐµÑа 63 if (windowpos >= scrollAmount) { 64 siteHeader.addClass('fixed-header'); 65 } else { 66 siteHeader.removeClass('fixed-header'); 67 } 68 } 69 } 70 headerStyle(); // ÐÑзов пÑи загÑÑзке 71 $(window).on('scroll', headerStyle); // ÐÑзов пÑи ÑкÑолле 72 73 74 /* 75 * ---------------------------------------------------------------------------------------- 76 * MAGNIFIC POPUP JS 77 * ---------------------------------------------------------------------------------------- 78 */ 79 if ($.fn.magnificPopup) { 80 // Image popups 81 $('.work-popup').magnificPopup({ 82 type: 'image', 83 removalDelay: 300, // Delay removal for animation 84 mainClass: 'mfp-with-zoom', // Animation class 85 gallery: { enabled: true }, // Enable gallery mode 86 zoom: { enabled: false } // Disable zoom effect if not needed 87 }); 88 // Iframe popups (Youtube, Vimeo, Google Maps) 89 $('.popup-youtube, .popup-vimeo, .popup-gmaps').magnificPopup({ 90 disableOn: 700, // Disable on smaller screens 91 type: 'iframe', 92 mainClass: 'mfp-fade', // Fade animation 93 removalDelay: 160, 94 preloader: false, // Disable preloader if not needed
95 fixedContentPos: false // Better handling on mobile 96 }); 97 } 98 99 /* 100 * ---------------------------------------------------------------------------------------- 101 * SCROLL TO UP JS 102 * ---------------------------------------------------------------------------------------- 103 */ 104 var progressWrap = document.querySelector('.progress-wrap'); 105 if (progressWrap) { 106 var progressPath = document.querySelector('.progress-wrap path'); 107 if (progressPath) { 108 var pathLength = 0; 109 try { 110 pathLength = progressPath.getTotalLength(); // May throw error in some environments 111 } catch (e) { 112 console.warn("Could not get total length of SVG path.", e); 113 progressWrap.style.display = 'none'; // Hide if error 114 } 115 116 if (pathLength > 0) { 117 progressPath.style.transition = progressPath.style.WebkitTransition = 'none'; 118 progressPath.style.strokeDasharray = pathLength + ' ' + pathLength; 119 progressPath.style.strokeDashoffset = pathLength; 120 progressPath.getBoundingClientRect(); // Trigger reflow 121 progressPath.style.transition = progressPath.style.WebkitTransition = 'stroke-dashoffset 10ms linear'; 122 123 var updateProgress = function() { 124 try { 125 var scroll = $(window).scrollTop(); 126 var height = $(document).height() - $(window).height(); 127 // Avoid division by zero and handle negative scroll values 128 var progress = height > 0 ? pathLength - (Math.max(0, scroll) * pathLength / height) : 0; 129 progressPath.style.strokeDashoffset = Math.max(0, Math.min(progress, pathLength)); 130 } catch (e) { 131 console.error("Error updating scroll progress:", e); 132 } 133 } 134 updateProgress(); // Initial update 135 $(window).on('scroll', updateProgress); // Update on scroll 136 137 var offset = 150; // Show button after scrolling this amount 138 var duration = 550; // Animation duration for scrolling to top 139 140 $(window).on('scroll', function() { 141 if ($(this).scrollTop() > offset) { 142 $(progressWrap).addClass('active-progress'); // Use jQuery for class manipulation 143 } else { 144 $(progressWrap).removeClass('active-progress'); 145 } 146 }); 147 148 $(progressWrap).on('click', function(event) { 149 event.preventDefault(); 150 $('html, body').animate({ scrollTop: 0 }, duration); 151 return false; 152 }); 153 } else { 154 progressWrap.style.display = 'none'; // Hide if pathLength is 0 155 } 156 } else { 157 if (progressWrap) progressWrap.style.display = 'none'; // Hide if path element not found 158 } 159 } 160 161 /* ========================================================================== 162 SCROLLER ANIMATION (Company Logos) - Optional, keep if used 163 ========================================================================== */ 164 const scrollers = document.querySelectorAll(".scroller"); 165 if (scrollers.length > 0) { 166 // Check for reduced motion preference 167 if (!window.matchMedia("(prefers-reduced-motion: reduce)").matches) { 168 addAnimation(); 169 } 170 171 function addAnimation() {
172 scrollers.forEach((scroller) => { 173 scroller.setAttribute("data-animated", true); // Mark as animated 174 175 const scrollerInner = scroller.querySelector(".scroller__inner"); 176 if (scrollerInner) { 177 const scrollerContent = Array.from(scrollerInner.children); 178 if (scrollerContent.length > 0) { 179 // Duplicate items for seamless scrolling 180 scrollerContent.forEach((item) => { 181 const duplicatedItem = item.cloneNode(true); 182 duplicatedItem.setAttribute("aria-hidden", true); // Hide from screen readers 183 scrollerInner.appendChild(duplicatedItem); 184 }); 185 } else { 186 console.warn("Scroller inner content is empty."); 187 } 188 } else { 189 console.warn("Scroller inner element not found."); 190 } 191 }); 192 } 193 } 194 195 /* 196 * ---------------------------------------------------------------------------------------- 197 * CUSTOM CURSOR JS - Optional, keep if used 198 * ---------------------------------------------------------------------------------------- 199 */ 200 const cursorBall = document.getElementById('ball'); // Assuming these IDs exist in HTML 201 const magicCursor = document.getElementById('magic-cursor'); 202 203 // Check if GSAP is loaded and elements exist 204 if (typeof gsap !== 'undefined' && cursorBall && magicCursor) { 205 gsap.set(magicCursor, { opacity: 0 }); // Initially hide
206 207 // Move cursor on mouse move 208 document.addEventListener('mousemove', function(e) { 209 gsap.to(magicCursor, { 210 duration: 0.3, 211 x: e.clientX, 212 y: e.clientY, 213 ease: 'power2.out' // Smooth easing 214 }); 215 }); 216 217 // Fade in/out on entering/leaving the window 218 document.addEventListener('mouseenter', () => { 219 gsap.to(magicCursor, { duration: 0.3, opacity: 1 }); 220 }); 221 document.addEventListener('mouseleave', () => { 222 gsap.to(magicCursor, { duration: 0.3, opacity: 0 }); 223 }); 224 225 // Scale effect on hovering specific elements 226 const hoverElements = document.querySelectorAll('a, button, .cursor-pointer'); // Select interactive elements 227 hoverElements.forEach(function(element) { 228 element.addEventListener('mouseenter', function() { 229 magicCursor.classList.add('hovered'); // Add class for potential style changes 230 gsap.to(magicCursor, { duration: 0.3, scale: 1.5, ease: 'power1.out' }); 231 }); 232 element.addEventListener('mouseleave', function() { 233 magicCursor.classList.remove('hovered'); 234 gsap.to(magicCursor, { duration: 0.3, scale: 1, ease: 'power2.out' }); 235 }); 236 }); 237 } else { 238 // console.log("GSAP or cursor elements not found, custom cursor disabled."); 239 } 240 241 /* 242 * ---------------------------------------------------------------------------------------- 243 * MOBILE MENU & DROPDOWN LOGIC (Revised for Clarity) 244 * ---------------------------------------------------------------------------------------- 245 */ 246 247 // --- Mobile Menu Toggle (Burger Click) --- 248 if ($('.mobile-menu-toggle').length) { 249 $('.mobile-menu-toggle').on('click', function(e) { 250 e.preventDefault(); // Prevent default button action 251 console.log("Mobile toggle clicked"); // Debug log 252 253 // Toggle class on body to control global state 254 $('body').toggleClass('mobile-menu-visible'); 255 256 // Toggle active class on the burger button itself (for styling the 'X') 257 $(this).toggleClass('active'); 258 259 // Toggle the visibility of the main menu container 260 var $navbarCollapse = $('.navbar-collapse'); 261 if ($navbarCollapse.length > 0) { 262 $navbarCollapse.slideToggle(300); // Use slide animation 263 } else { 264 console.error("'.navbar-collapse' element not found for mobile menu."); 265 } 266 267 // If opening the menu, ensure no submenus are lingering open 268 if ($('body').hasClass('mobile-menu-visible')) { 269 // Optionally close any open submenus when main menu opens 270 // $('.main-header .navigation li.has-children.submenu-open') 271 // .removeClass('submenu-open') 272 // .children('ul.sub-menu').hide(); // Hide immediately 273 // $('.main-header .navigation li.has-children .dropdown-btn i').removeClass('rotate-180'); 274 } else { 275 // If closing the main menu, ensure all submenus are closed too 276 $('.main-header .navigation li.has-children.submenu-open') 277 .removeClass('submenu-open') 278 .children('ul.sub-menu').slideUp(200); // Animate closing 279 $('.main-header .navigation li.has-children .dropdown-btn i').removeClass('rotate-180'); 280 } 281 }); 282 } 283 284 // --- Dropdown Submenu Logic --- 285 if ($('.main-header .navigation li.has-children').length) { 286 // Add dropdown button IF it doesn't exist 287 $('.main-header .navigation li.has-children').each(function() { 288 if ($(this).find('.dropdown-btn').length === 0) { 289 $(this).append('<button type="button" class="dropdown-btn"><i class="ri-arrow-down-s-line"></i></button>'); 290 } 291 }); 292 293 // --- Click on Dropdown Button (Arrow) --- 294 // Use event delegation for dynamically added buttons 295 $('.main-header .navigation').on('click', 'li.has-children > .dropdown-btn', function(e) { 296 e.preventDefault(); // Prevent button default action 297 e.stopPropagation(); // Stop click from bubbling up to parent li/a 298 console.log("Submenu toggle button clicked"); // Debug log 299 300 var $parentLi = $(this).closest('li'); // Get the parent LI 301 var $subMenu = $parentLi.children('ul.sub-menu'); // Get the direct child submenu 302 303 // --- Close other open submenus on the same level --- 304 $parentLi.siblings('.has-children.submenu-open').each(function(){ 305 $(this).removeClass('submenu-open'); // Remove class 306 $(this).children('ul.sub-menu').slideUp(300); // Animate closing 307 $(this).find('.dropdown-btn i').removeClass('rotate-180'); // Reset icon rotation 308 }); 309 310 // --- Toggle the current submenu --- 311 $parentLi.toggleClass('submenu-open'); // Toggle state class on LI 312 $subMenu.slideToggle(300); // Animate open/close 313 $(this).find('i').toggleClass('rotate-180'); // Toggle icon rotation class 314 }); 315 316 // --- Click on Parent Link (e.g., <a href="#">) on Mobile --- 317 // Use event delegation 318 $('.main-header .navigation').on('click', 'li.has-children > a', function(e) { 319 // Check if on mobile viewport AND the link is effectively '#' or empty 320 if ($(window).innerWidth() < 992 && ($(this).attr('href') === '#' || $(this).attr('href') === '' || $(this).attr('href') === '/#')) { // Added '/#' check 321 e.preventDefault(); // Prevent jumping to top or navigating 322 console.log("Parent link (# or empty) clicked on mobile"); // Debug log 323 324 // Trigger a click on the sibling dropdown button to open/close the submenu 325 // Use .find() relative to parent LI to be safer 326 $(this).closest('li').find('.dropdown-btn').trigger('click'); 327 } 328 // If it's a real link or on desktop, the link will navigate as usual (and the menu closing logic at the top will handle it) 329 }); 330 } 331 332 333 // ## Testimonials Active - Optional, keep if used 334 if ($('.testimonials-wrap').length && $.fn.slick) { 335 $('.testimonials-wrap').slick({ 336 dots: false, 337 infinite: true, 338 autoplay: true, 339 autoplaySpeed: 5000, // Slower speed 340 arrows: true, 341 speed: 1000, 342 focusOnSelect: false, 343 prevArrow: '.testimonial-prev', // Ensure these selectors exist in HTML 344 nextArrow: '.testimonial-next', // Ensure these selectors exist in HTML 345 slidesToShow: 2, 346 slidesToScroll: 1, 347 responsive: [ 348 { 349 breakpoint: 767, // On screens smaller than 767px 350 settings: { 351 slidesToShow: 1 // Show only 1 testimonial 352 } 353 } 354 ] 355 }); 356 } 357 358 // ## Project Filter - Optional, keep if used and Isotope is loaded 359 if ($('.project-filter li').length && $('.project-masonry-active').length) { 360 // Check if Isotope and imagesLoaded are available 361 if (typeof $.fn.imagesLoaded !== 'undefined' && typeof $.fn.isotope !== 'undefined') { 362 var $container = $('.project-masonry-active'); 363 364 // Initialize Isotope after images have loaded 365 $container.imagesLoaded( function() { 366 $container.isotope({ 367 itemSelector: '.item', // Selector for grid items 368 layoutMode: 'masonry' // Or 'fitRows', etc. 369 }); 370 }); 371 372 // Filter items on button click 373 $(".project-filter li").on('click', function() { 374 $(".project-filter li").removeClass("current"); // Remove active class from all 375 $(this).addClass("current"); // Add active class to clicked item 376 377 var selector = $(this).attr('data-filter'); // Get the filter value 378 379 // Apply the filter to Isotope 380 $container.isotope({ 381 filter: selector 382 }); 383 }); 384 } else { 385 console.warn("Isotope or imagesLoaded jQuery plugin not available for project filter."); 386 } 387 } 388 389 /* ## Fact Counter - Optional, keep if used and Appear.js is loaded */ 390 if ($('.counter-text-wrap').length && $.fn.appear) { 391 $('.counter-text-wrap').appear(function() { 392 var $t = $(this); 393 // Check if it hasn't been counted already 394 if (!$t.hasClass("counted")) { 395 $t.addClass("counted"); // Mark as counted 396 397 var $countText = $t.find(".count-text"); 398 var stop = parseInt($countText.attr("data-stop"), 10); 399 var speed = parseInt($countText.attr("data-speed"), 10) || 2000; // Default speed 400 401 // Animate the count 402 $({ countNum: $countText.text() }).animate({ countNum: stop }, { 403 duration: speed, 404 easing: "linear", // Or 'swing' 405 step: function() { 406 $countText.text(Math.floor(this.countNum)); 407 }, 408 complete: function() { 409 $countText.text(this.countNum); // Ensure final value is set 410 } 411 }); 412 } 413 }, { accY: 0 });
413 // Trigger when the element is 0px below the viewport 414 } 415 416 // ## Scroll to Target - Optional, keep if used 417 if ($('.scroll-to-target').length) { 418 $(".scroll-to-target").on('click', function(e) { 419 e.preventDefault(); // Prevent default anchor behavior 420 var targetSelector = $(this).attr('data-target'); // Get target selector from data attribute 421 var $target = $(targetSelector); 422 423 if ($target.length) { // Check if target exists 424 $('html, body').animate({ 425 scrollTop: $target.offset().top // Scroll to target's top offset 426 }, 1000); // Animation duration 427 } else { 428 console.warn("Scroll target not found:", targetSelector); 429 } 430 return false; // Prevent further event propagation 431 }); 432 } 433 434 // ## Nice Select - Optional, keep if used and NiceSelect is loaded 435 if ($.fn.niceSelect) { 436 try { 437 $('select').niceSelect(); // Apply NiceSelect to all select elements 438 } catch(e) { 439 console.warn("NiceSelect initialization error:", e); 440 } 441 } 442 443 // ## WOW Animation - Optional, keep if used and WOW.js is loaded 444 if (typeof WOW === 'function') { 445 try { 446 new WOW({ 447 mobile: false // Disable WOW animations on mobile devices (optional) 448 }).init(); 449 } catch(e) { 450 console.warn("WOW.js initialization error:", e); 451 } 452 } 453 454 }); // --- End document.ready --- 455 456 457 /* ========================================================================== 458 When document is loaded, do (Preloader, Isotope init) 459 ========================================================================== */ 460 $(window).on('load', function() { 461 462 // --- Preloader Animation --- 463 const preloader = document.querySelector(".preloader"); 464 const svg = document.getElementById("preloaderSvg"); // Ensure this ID exists in HTML 465 const loadText = document.querySelector(".preloader-heading .load-text"); // Ensure this selector exists 466 467 // Check if GSAP is loaded and elements exist 468 if (preloader && svg && loadText && typeof gsap !== 'undefined') { 469 const tl = gsap.timeline({ 470 onComplete: () => { // Function to run after timeline completes 471 if (preloader) { 472 // Use GSAP for smoother fade out/removal 473 gsap.to(preloader, { duration: 0.3, opacity: 0, onComplete: () => preloader.style.display = "none" }); 474 } 475 } 476 }); 477 478 const curve = "M0 502S175 272 500 272s500 230 500 230V0H0Z"; // SVG path for curve animation 479 const flat = "M0 2S175 1 500 1s500 1 500 1V0H0Z"; // SVG path for flat animation 480 481 tl.to(loadText, { delay: 1.2, y: -100, opacity: 0 }); // Animate text out 482 tl.to(svg, { duration: 0.5, attr: { d: curve }, ease: "power2.easeIn" }) // Morph SVG to curve 483 .to(svg, { duration: 0.5, attr: { d: flat }, ease: "power2.easeOut" }); // Morph SVG to flat 484 tl.to(preloader, { duration: 0.7, y: "-110%", ease: "power2.in" }, "-=0.5"); // Animate preloader up slightly overlapping 485 // tl.set(preloader, { display: "none" }); // Handled by onComplete now 486 487 } else { 488 // Fallback if GSAP or elements are missing 489 if (preloader) { 490 preloader.style.display = "none"; 491 console.log("Preloader hidden via fallback (GSAP or elements missing)."); 492 } 493 } 494 495 // --- Initialize Isotope Layouts After Images Loaded --- 496 // Ensure Isotope and imagesLoaded are available 497 if (typeof $.fn.imagesLoaded !== 'undefined' && typeof $.fn.isotope !== 'undefined') { 498 // Project Masonry 499 var $masonry = $('.project-masonry-active'); 500 if ($masonry.length) { 501 $masonry.imagesLoaded( function() { 502 $masonry.isotope({ 503 itemSelector: '.item', 504 layoutMode: 'masonry' // Or 'fitRows' 505 }); 506 }); 507 } 508 509 // Blog Standard Layout (if using Masonry) 510 var $blogWrap = $('.blog-standard-wrap'); // Ensure this class exists on the container 511 if ($blogWrap.length) { 512 $blogWrap.imagesLoaded( function() { 513 $blogWrap.isotope({ 514 itemSelector: '.item', // Ensure blog items have this class 515 layoutMode: 'masonry' // Or 'fitRows' 516 }); 517 }); 518 } 519 } else { 520 // console.warn("Isotope or imagesLoaded not available for layout initialization."); 521 } 522 523 }); // --- End window.load --- 524 525})(window.jQuery); // Pass jQuery to the closure
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.