PageSourceSearch

https://spinmamalogin.gr/js/script.js

js spinmamalogin.gr collected 2026-10-06 00:29:38 UTC 26,905 bytes, 525 lines download raw bytes

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.