PageSourceSearch

https://samui-island-realty.com/wp-content/themes/houzez/js/custom.js?ver=4.3.2

js samui-island-realty.com collected 2026-10-05 03:41:54 UTC 297,099 bytes, 7,645 lines download raw bytes

1/**
2 * Houzez Custom JavaScript
3 * Consolidated version of all Houzez JavaScript modules
4 *
5 * This file combines the following modules:
6 * - Core: Basic functionality and utilities
7 * - Login: Authentication functionality
8 * - Favorites: Property favorites management
9 * - Compare: Property comparison features
10 * - Properties: Property-specific functionality
11 * - Pagination: AJAX pagination and infinite loading
12 * - Elementor: Integration with Elementor page builder
13 */
14(function ($) {
15    'use strict';
16
17    // Main Houzez object that will contain all modules
18    var houzez = window.houzez || {};
19
20    /**
21     * Core Module
22     * Core functionality and utility functions
23     */
24    houzez.Core = (function () {
25        // Cache DOM selectors
26        const $document = $(document);
27        const $window = $(window);
28
29        // Global variables
30        const $win = $(window);
31        const $body = $('body');
32
33        // Centralized config from global vars
34        const config = {
35            // Core config
36            ajaxurl: houzez_vars.admin_url + 'admin-ajax.php',
37            userID: houzez_vars.user_id,
38            primary_color: houzez_vars.primary_color,
39            houzez_rtl: houzez_vars.houzez_rtl === 'yes',
40            wp_is_mobile: houzez_vars.wp_is_mobile,
41            currency_position: houzez_vars.currency_position,
42            currency_symbol: houzez_vars.currency_symbol,
43            decimals: houzez_vars.decimals,
44            decimal_point_separator: houzez_vars.decimal_point_separator,
45            thousands_separator: houzez_vars.thousands_separator,
46            is_singular_property: houzez_vars.is_singular_property,
47            property_detail_nav: houzez_vars.prop_detail_nav,
48            processing_text: houzez_vars.processing_text,
49            houzez_is_splash: houzez_vars.houzez_is_splash,
50            wpadminbar_height: $('#wpadminbar').outerHeight() || 0,
51            // Login config
52            login_loading: houzez_vars.login_loading,
53            login_redirect: houzez_vars.login_redirect,
54            redirect_type: houzez_vars.redirect_type,
55            houzez_reCaptcha: parseInt(houzez_vars.houzez_reCaptcha),
56            g_recaptha_version: houzez_vars.g_recaptha_version,
57
58            // Favorites config
59            favorite_url: houzez_vars.favorite_url || '',
60            add_to_favorite_login_required: parseInt(
61                houzez_vars.add_to_favorite_login_required || 0
62            ),
63
64            // Compare config
65            compare_url: houzez_vars.compare_url || '',
66            compare_add_icon: houzez_vars.compare_add_icon || '',
67            compare_remove_icon: houzez_vars.compare_remove_icon || '',
68            add_compare_text: houzez_vars.add_compare_text || '',
69            remove_compare_text: houzez_vars.remove_compare_text || '',
70            compare_limit: houzez_vars.compare_limit || '',
71            compare_page_not_found: houzez_vars.compare_page_not_found || '',
72
73            // Sliders config
74            prev_text: houzez_vars.prev_text || 'Prev',
75            next_text: houzez_vars.next_text || 'Next',
76            disable_property_gallery: houzez_vars.disable_property_gallery || 0,
77            grid_gallery_behaviour: houzez_vars.grid_gallery_behaviour || '',
78
79            // Properties config
80            // ...
81
82            // Pagination config
83            // ...
84
85            // New config for half map and fullscreen sections
86            is_halfmap: parseInt(houzez_vars.is_halfmap),
87        };
88
89        // Utility functions
90        const util = {
91            /**
92             * Debounce function to limit rapid function calls
93             * @param {Function} func - Function to debounce
94             * @param {number} delay - Delay in milliseconds
95             * @return {Function} - Debounced function
96             */
97            debounce: function (func, delay) {
98                let debounceTimer;
99                return function () {
100                    const context = this;
101                    const args = arguments;
102                    clearTimeout(debounceTimer);
103                    debounceTimer = setTimeout(
104                        () =>
104 func.apply(context, args),
105                        delay
106                    );
107                };
108            },
109
110            /**
111             * Parse a string to boolean
112             * @param {string} str - String to parse
113             * @return {boolean} - Parsed boolean
114             */
115            parseBool: function (str) {
116                if (typeof str === 'string') {
117                    switch (str.toLowerCase()) {
118                        case 'true':
119                        case '1':
120                        case 'yes':
121                        case 'y':
122                            return true;
123                        case 'false':
124                        case '0':
125                        case 'no':
126                        case 'n':
127                            return false;
128                        default:
129                            return Boolean(str);
130                    }
131                } else {
132                    return Boolean(str);
133                }
134            },
135
136            /**
137             * Format number with thousands separator
138             * @param {number} number - Number to format
139             * @return {string} - Formatted number
140             */
141            thousandSeparator: function (n) {
142                if (typeof n === 'number') {
143                    n += '';
144                    const x = n.split('.');
145                    const x1 = x[0];
146                    const x2 = x.length > 1 ? '.' + x[1] : '';
147                    const rgx = /(\d+)(\d{3})/;
148
149                    let result = x1;
150                    while (rgx.test(result)) {
151                        result = result.replace(
152                            rgx,
153                            '$1' + config.thousands_separator + '$2'
154                        );
155                    }
156                    return result + x2;
157                }
158                return n;
159            },
160
161            /**
162             * Format currency values
163             * @param {string|number} price_value - Price to format
164             * @return {string} - Formatted price with currency symbol
165             */
166            currencyFormat: function (price_value) {
167                if (!config.currency_position || !config.currency_symbol) {
168                    return price_value;
169                }
170
171                return config.currency_position === 'after'
172                    ? price_value + config.currency_symbol
173                    : config.currency_symbol + price_value;
174            },
175
176            /**
177             * Format numbers with proper separators
178             * @param {number} number - Number to format
179             * @return {string} - Formatted number
180             */
181            numberFormat: function (number, custom = false) {
182                const dec_point = config.decimal_point_separator;
183                const thousands_sep = config.thousands_separator;
184
185                let i, j, kw, kd, km;
186                let decimals = config.decimals;
187
188                // Input sanitation & defaults
189                if (isNaN(decimals) || custom) {
190                    decimals = 2;
191                }
192
193                i = parseInt((number = (+number || 0).toFixed(decimals))) + '';
194                j = (j = i.length) > 3 ? j % 3 : 0;
195
196                km = j ? i.substr(0, j) + thousands_sep : '';
197                kw = i
198                    .substr(j)
199                    .replace(/(\d{3})(?=\d)/g, '$1' + thousands_sep);
200
201                kd = decimals
202                    ? dec_point +
203                      Math.abs(number - i)
204                          .toFixed(decimals)
205                          .replace(/-/, 0)
206                          .slice(2)
207                    : '';
208
209                return km + kw + kd;
210            },
211
212            /**
213             * Get cookie by name
214             * @param {string} name - Cookie name
215             * @return {string} - Cookie value
216             */
217            getCookie: function (name) {
218                const nameEQ = name + '=';
219                const ca = document.cookie.split(';');
220                for (let i = 0; i < ca.length; i++) {
221                    let c = ca[i];
222                    while (c.charAt(0) === ' ') c = c.substring(1, c.length);
223                    if (c.indexOf(nameEQ) === 0) {
224                        return c.substring(nameEQ.length, c.length);
225                    }
226                }
227                return null;
228            },
229
230            /**
231             * Set cookie
232             * @param {string} name - Cookie name
233             * @param {string} value - Cookie value
234             * @param {number} days - Days until cookie expires
235             */
236            setCookie: function (name, value, days) {
237                let expires = '';
238                if (days) {
239                    const date = new Date();
240                    date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
241                    expires = '; expires=' + date.toUTCString();
242                }
243                document.cookie = name + '=' + value + expires + '; path=/';
244            },
245
246            /**
247             * Delete cookie
248             * @param {string} name - Cookie name
249             */
250            deleteCookie: function (name) {
251                this.setCookie(name, '', -1);
252            },
253
254            /**
255             * Add commas to number for display
256             * @param {string|number} nStr - Number to format
257             * @returns {string} Formatted number
258             */
259            addCommas: function (nStr) {
260                nStr += '';
261                const x = nStr.split('.');
262                let x1 = x[0];
263                const x2 = x.length > 1 ? '.' + x[1] : '';
264                const rgx = /(\d+)(\d{3})/;
265                while (rgx.test(x1)) {
266                    x1 = x1.replace(rgx, '$1' + ',' + '$2');
267                }
268                return x1 + x2;
269            },
270
271            /**
272             * Format number with commas
273             * @param {number} number - Number to format
274             * @returns {string} Formatted number
275             */
276            formatNumber: function (number) {
277                const parts = number.toString().split('.');
278                parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
279                return parts.join('.');
280            },
281
282            /**
283             * Show the processing modal
284             * @param {string} msg - Message to display
285             */
286            processingModal: function (msg) {
287                var process_modal =
288                    '<div class="modal fade" id="fave_modal" tabindex="-1" role="dialog" aria-labelledby="faveModalLabel" aria-hidden="true"><div class="modal-dialog"><div class="modal-content"><div class="modal-body houzez_messages_modal">' +
289                    msg +
290                    '</div></div></div></div>';
291
292                jQuery('body').append(process_modal);
293                jQuery('#fave_modal').modal('show');
294            },
295
296            /**
297             * Close the processing modal
298             */
299            processingModalClose: function () {
300                jQuery('#fave_modal').modal('hide');
301            },
302
303            /**
304             * Generate loader HTML for AJAX requests
305             * @returns {string} Loader HTML markup
306             */
307            getLoaderHtml: function () {
308                return '<div class="houzez-overlay-loading"><div class="overlay-placeholder"><div class="loader-ripple spinner"><div class="bounce1"></div><div class="bounce2"></div><div class="bounce3"></div></div></div></div>';
309            },
310
311            /**
312             * Generate success alert HTML
313             * @param {string} message - Message to display
314             * @returns {string} Success alert HTML
315             */
316            getSuccessHtml: function (message) {
317                return (
318                    '<div class="alert alert-success alert-dismissible fade show" role="alert">' +
319                    '<i class="houzez-icon icon-check-circle-1 me-1"></i> ' +
320                    message +
321                    '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>' +
322                    '</div>'
323                );
324            },
325
326            /**
327             * Generate error alert HTML
328             * @param {string} message - Message to display
329             * @returns {string} Error alert HTML
330             */
331            getErrorHtml: function (message) {
332                return (
333                    '<div class="alert alert-danger alert-dismissible fade show" role="alert">' +
334                    '<i class="houzez-icon icon-remove-circle me-1"></i> ' +
335                    message +
336                    '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>' +
337                    '</div>'
338                );
339            },
340
341            /**
342             * Display success message in target element
343             * @param {jQuery|string} target - Target element or selector
344             * @param {string} message - Message to display
345             */
346            showSuccess: function (target, message) {
347                const $target = typeof target === 'string' ? $(target) : target;
348                $target.empty().append(this.getSuccessHtml(message));
349            },
350
351            /**
352             * Display error message in target element
353             * @param {jQuery|string} target - Target element or selector
354             * @param {string} message - Message to display
355             */
356            showError: function (target, message) {
357                const $target = typeof target === 'string' ? $(target) : target;
358                $target.empty().append(this.getErrorHtml(message));
359            },
360
361            /**
362             * Calculate monthly mortgage payment
363             * @param {number} principal - Principal loan amount
364             * @param {number} annualInterestRate - Annual interest rate (in percentage)
365             * @param {number} loanTermInYears - Loan term in years
366             * @return {number} - Monthly payment amount
367             */
368            calculateMonthlyPayment: function (
369                principal,
370                annualInterestRate,
371                loanTermInYears
372            ) {
373                const monthlyInterestRate = annualInterestRate / 12 / 100;
374                const numberOfMonths = loanTermInYears * 12;
375
376                if (monthlyInterestRate === 0) {
377                    return principal / numberOfMonths;
378                }
379
380                return (
381                    (principal *
382                        (monthlyInterestRate *
383                            Math.pow(
384                                1 + monthlyInterestRate,
385                                numberOfMonths
386                            ))) /
387                    (Math.pow(1 + monthlyInterestRate, numberOfMonths) - 1)
388                );
389            },
390
391            /**
392             * Parse and validate input, removing non-numeric characters
393             * @param {string} selector - Input selector
394             * @return {number} - Parsed value or 0 if invalid/empty
395             */
396            parseNumberInput: function (selector) {
397                const rawValue = $(selector).val();
398
399                if (!rawValue) return 0; // if value is empty or undefined, return 0
400
401                // Remove any non-digit character except the decimal point
402                const cleanedValue = rawValue.replace(/[^0-9.]/g, '');
403
404                // Parsing to number
405                const parsedValue = parseFloat(cleanedValue);
406
407                // if parsedValue is NaN, return 0, otherwise return the parsed number
408                return isNaN(parsedValue) ? 0 : parsedValue;
409            },
410
411            /**
412             * Update URL with query parameter and return the new URL string
413             * @param {string} key - Parameter key
414             * @param {string} value - Parameter value
415             * @returns {string} The new URL with the updated parameter
416             */
417            updateUrlParam: function (key, value) {
418                var pathname = window.location.pathname;
419                var urlParams = new URLSearchParams(window.location.search);
420                urlParams.set(key, value);
421
422                // If sorting is being changed, remove pagination to start from page 1
423                if (key === 'sortby') {
424                    // Remove WordPress permalink-based pagination (e.g., /page/2/)
425                    pathname = pathname.replace(/\/page\/\d+\/?/, '/');
426
427                    // Also remove query-based pagination parameters
428                    urlParams.delete('paged');
429                    urlParams.delete('page');
430                }
431
432                var baseUrl = window.location.origin + pathname;
433                // Preserve other existing query parameters
434                var newUrl = baseUrl + '?' + urlParams.toString();
435                return newUrl;
436            },
437
438            /**
439             * Calculate heights for half map and fullscreen sections
440             */
441            calculateSectionHeight: function () {
442                const windowHeight = $window.height();
443                const headerDesktop = $('.header-desktop').outerHeight() || 0;
444                const headerMobile = $('.header-mobile').outerHeight() || 0;
445                const topBar = $('.top-bar-wrap').outerHeight() || 0;
446                const headerElementor =
447                    $('#header-hz-elementor').outerHeight() || 0;
448                let adminBarHeight = houzez.Core.config.wpadminbar_height;
449                let halfMapWrapHeight = 0;
450
451                const mobileSearchNav =
452                    $('.mobile-search-nav').outerHeight() || 0;
453                const desktopSearchNav =
454                    $('.desktop-search-nav').outerHeight() || 0;
455
456                // Use headerElementor as the only header height if present
457                let headerHeight;
458                if (headerElementor > 0) {
459                    headerHeight =
460                        headerElementor +
461                        (window.innerWidth > 991
462                            ? desktopSearchNav + topBar
463                            : mobileSearchNav);
464                } else {
465                    headerHeight =
466                        window.innerWidth > 991
467                            ? headerDesktop + desktopSearchNav + topBar
468                            : headerMobile + mobileSearchNav;
469                }
470
471                let halfMapHeight =
472                    windowHeight - headerHeight - adminBarHeight;
473                halfMapWrapHeight =
474                    windowHeight + headerHeight + adminBarHeight;
475
476                if (halfMapHeight < 0) {
477                    halfMapHeight = 0;
478                }
479
480                if (window.innerWidth > 768) {
481                    $('.half-map-wrap').height(halfMapHeight);
482                    $('.main-half-map-wrap').css({
483                        'max-height': halfMapWrapHeight + 'px',
484                        height: 'calc(100vh - ' + adminBarHeight + 'px)',
485                    });
486                } else {
487                    $('.half-map-left-wrap').height(halfMapHeight);
488                }
489
490                $('.top-banner-wrap-fullscreen').height(
491                    windowHeight - headerHeight
492                );
493                $('.top-banner-wrap-fullscreen .property-slider').height(
494                    windowHeight - headerHeight
495                );
496                $(
497                    '.top-banner-wrap-fullscreen .property-slider-item-wrap'
498                ).height(windowHeight - headerHeight);
499            },
500
501            /**
502             * Reset captcha (supports both Google reCaptcha and Cloudflare Turnstile)
503             * @param {jQuery} $form - Form element containing captcha
504             */
505            resetCaptcha: function($form) {
506                // Remove any existing captcha responses
507                $form.find('.g-recaptcha-response').remove();
508                $form.find('.cf-turnstile-response').remove();
509
510                // Check if reCaptcha is active
511                if (config.houzez_reCaptcha == 1) {
512                    if (config.g_recaptha_version == 'v3') {
513                        // reCaptcha v3 - regenerate token
514                        if (typeof houzezReCaptchaLoad === 'function') {
515                            houzezReCaptchaLoad();
516                        }
517                    } else {
518                        // reCaptcha v2 - reset widget
519                        if (typeof houzezReCaptchaReset === 'function') {
520                            houzezReCaptchaReset();
521                        }
522                    }
523                }
524                // Check if Turnstile is active (check for widget on page)
525                else if ($form.find('.houzez-turnstile').length > 0) {
526                    if (typeof houzezTurnstileReset === 'function') {
527                        houzezTurnstileReset();
528                    } else if (typeof turnstile !== 'undefined') {
529                        $form.find('.houzez-turnstile').each(function() {
530                            turnstile.reset(this);
531                        });
532                    }
533                }
534            },
535        };
536
537        /**
538         * Check if the device is mobile
539         */
540        const is_mobile = () => {
541            // 1. matchMedia (layout)
542            if (window.matchMedia('(max-width: 991px)').matches) {
543                return true;
544            }
545
546            // 2. modern UAData
547            if (navigator.userAgentData?.mobile === true) {
548                return true;
549            }
550
551            // 3. UA regex fallback
552            return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
553                navigator.userAgent
554            );
555        };
556
557        const sortby_mobile = () => {
558            if (is_mobile()) {
559                $('#ajax_sort_properties').addClass('mobile-sortby');
560                $('#mobile-search-form').addClass('apply-mobile-pagination');
561            } else {
562                $('#ajax_sort_properties').removeClass('mobile-sortby');
563                $('#mobile-search-form').removeClass('apply-mobile-pagination');
564            }
565        };
566
567        /**
568         * Initialize tooltips
569         */
570        const initTooltips = function () {
571            const tooltipTriggerList = [].slice.call(
572                document.querySelectorAll('[data-bs-toggle="tooltip"]')
573            );
574            tooltipTriggerList.map(function (tooltipTriggerEl) {
575                // Check if tooltip already exists to prevent duplicates
576                const existingTooltip =
577                    bootstrap.Tooltip.getInstance(tooltipTriggerEl);
578                if (!existingTooltip) {
579                    return new bootstrap.Tooltip(tooltipTriggerEl);
580                }
581                return existingTooltip;
582            });
583        };
584
585        /**
586         * Hide all tooltips
587         */
588        const hideAllTooltips = function () {
589            const tooltips = document.querySelectorAll(
590                '[data-bs-toggle="tooltip"]'
591            );
592            tooltips.forEach(function (element) {
593                const tooltipInstance = bootstrap.Tooltip.getInstance(element);
594                if (tooltipInstance) {
595                    tooltipInstance.hide();
596                }
597            });
598        };
599
600        /**
601         * Initialize reviews tab from all reviews link
602         */
603        const initReviewsTab = function () {
604            $('.all-reviews').on('click', function (e) {
605                e.preventDefault();
606                $('.hz-review-tab').trigger('click');
607            });
608        };
609
610        /**
611         * Initialize property sorting
612         */
613        const initPropertySorting = function () {
614            $('#sort_properties').on('change', function () {
615                let key = 'sortby';
616                let value = $(this).val();
617                // Use the new Core utility function and reload
618                window.location.href = houzez.Core.util.updateUrlParam(
619                    key,
620                    value
621                );
622            });
623        };
624
625        /**
626         * Initialize modals with dynamic content
627         */
628        // const initDynamicModals = function () {
629        //     $('.schedule-contact-btn').on('click', function (e) {
630        //         e.preventDefault();
631        //         $('#schedule-contact-modal').modal('show');
632        //     });
633
634        //     // Membership selection modals
635        //     $('.houzez-open-membership-modal').on('click', function (e) {
636        //         e.preventDefault();
637        //         $('#modal-membership').modal('show');
638        //     });
639        // };
640
641        /**
642         * Toggle mobile menus and panels
643         */
644        const mobileToggleMenu = function () {
645            // Main navigation
646            $('.toggle-mobile-nav').on('click', function (e) {
647                e.preventDefault();
648                $('#mobile-main-nav').toggleClass('show');
649                $('.toggle-mobile-nav').toggleClass('active');
650            });
651
652            // Account navigation
653            $('.user-icon').on('click', function (e) {
654                e.preventDefault();
655                if ($('#user-login-dropdown').is(':visible')) {
656                    $('#user-login-dropdown').slideUp();
657                } else {
658                    $('#user-login-dropdown').slideDown();
659                }
660            });
661        };
662
663        /**
664         * Handle call and email popup modals from grid items
665         */
666        const gridCallToAction = function () {
667            // Handle call popup clicks
668            $('.hz-call-popup-js').on('click', function () {
669                var call_model_id = $(this).data('model-id');
670                $('#' + call_model_id).appendTo('body');
671                $('#' + call_model_id).modal('show');
672            });
673
674            // Handle email popup clicks
675            $('.hz-email-popup-js').on('click', function () {
676                var email_model_id = $(this).data('model-id');
677                $('#' + email_model_id).appendTo('body');
678                $('#' + email_model_id).modal('show');
679            });
680        };
681
682        $('.agent-show-onClick').on('click', function (e) {
683            $(this).toggleClass('agent-phone-hidden');
684        });
685
686        /**
687         * Handle scroll to top functionality
688         */
689        const scrollToTop = function () {
690            const scroll_anchor = $('#scroll-top');
691
692            $(window).on('scroll', function () {
693                if ($(this).scrollTop() > 500) {
694                    scroll_anchor.fadeIn('fast').css('display', 'block');
695                    return;
696                }
697                scroll_anchor.fadeOut('fast');
698            });
699
700            scroll_anchor.on('click', function (event) {
701                event.preventDefault();
702                $('html, body').animate({ scrollTop: 0 }, 'slow');
703            });
704        };
705
706        /**
707         * Reinitialize necessary functions after new content is loaded via AJAX
708         * Common utility for all modules that load content dynamically
709         */
710        const reinitializeAjaxFunctions = function () {
711            // Reinitialize favorites
712            if (houzez.Favorites) {
713                houzez.Favorites.initAddFavorite();
714            }
715
716            if (houzez.Compare) {
717                houzez.Compare.compare_for_ajax();
718            }
719
720            // Reinitialize grid image gallery
721            if (houzez.Sliders) {
722                houzez.Sliders.gridImageGallery();
723            }
724
725            if (houzez.PropertyPreview) {
726                houzez.PropertyPreview.init();
727            }
728
729            // Reinitialize tooltips
730            initTooltips();
731
732            // Reinitialize lazy loading
733            houzezLazyload();
734
735            // Reinitialize grid call to action
736            gridCallToAction();
737        };
738
739        /**
740         * Handle property view switching (list/grid)
741         */
742        const listingViewSwitch = function () {
743            // Handle view switching with URL update
744            $('.listing-switch-view .switch-btn, .switch-btn').on(
745                'click',
746                function (e) {
747                    e.preventDefault();
748                    var view = $(this).hasClass('btn-grid') ? 'grid' : 'list';
749
750                    // Update URL and reload page
751                    window.location.href = util.updateUrlParam(
752                        'listing-view',
753                        view
754                    );
755                }
756            );
757
758            // Set initial active state for buttons based on data-view attribute
759            var currentView = $('.listing-view').attr('data-view');
760            if (currentView) {
761                var $btnGrid = $('.btn-grid');
762                var $btnList = $('.btn-list');
763
764                if (currentView === 'grid') {
765                    $btnGrid.addClass('active').attr('aria-pressed', 'true');
766                    $btnList
767                        .removeClass('active')
768                        .attr('aria-pressed', 'false');
769                } else {
770                    $btnList.addClass('active').attr('aria-pressed', 'true');
771                    $btnGrid
772                        .removeClass('active')
773                        .attr('aria-pressed', 'false');
774                }
775            }
776        };
777
778        /**
779         * Handle lazy loading of images with .houzez-lazyload class
780         */
781        const houzezLazyload = function () {
782            // Check if any .houzez-lazyload elements exist before initializing
783            if (document.querySelectorAll('.houzez-lazyload').length === 0) {
784                return;
785            }
786
787            // core logic: find all .houzez-lazyload images and observe them
788            function init() {
789                var lazyImages = [].slice.call(
790                    document.querySelectorAll('.houzez-lazyload')
791                );
792                if (!lazyImages.length || !('IntersectionObserver' in window))
793                    return;
794
795                var observer = new IntersectionObserver(function (entries) {
796                    entries.forEach(function (entry) {
797                        if (!entry.isIntersecting) return;
798                        var img = entry.target;
799                        img.src = img.dataset.src;
800                        if (img.dataset.srcset) {
801                            img.srcset = img.dataset.srcset;
802                        }
803                        img.classList.remove('houzez-lazyload');
804                        observer.unobserve(img);
805                    });
806                });
807
808                lazyImages.forEach(function (img) {
809                    observer.observe(img);
810                });
811            }
812
813            // if DOM not ready, wait; otherwise run now
814            if (document.readyState === 'loading') {
815                document.addEventListener('DOMContentLoaded', init);
816            } else {
817                init();
818            }
819        };
820
821        /**
822         * Handle login and register trigger
823         */
824        const LoginRegisterTrigger = function () {
825            // Common function to toggle between login and register forms
826            const toggleForms = function (showLogin) {
827                // Toggle modal buttons
828                $('.modal-toggle-1').toggleClass('active', showLogin);
829                $('.modal-toggle-2').toggleClass('active', !showLogin);
830
831                // Toggle form visibility
832                $('.login-form-tab').toggleClass('active show', showLogin);
833                $('.register-form-tab').toggleClass('active show', !showLogin);
834            };
835
836            // Event handlers
837            $('.login-link a').on('click', function (e) {
838                e.preventDefault();
839                toggleForms(true);
840            });
841
842            $('.register-link a').on('click', function (e) {
843                e.preventDefault();
844                toggleForms(false);
845            });
846        };
847
848        /**
849         * Handle sidebar menu toggle
850         */
851        const sidebarMenuToggle = function () {
852            $('.menu-btn').on('click', function () {
853                $('body').toggleClass('sidebar-collapsed');
854            });
855
856            $('.crose-btn').on('click', function () {
857                $('body').removeClass('sidebar-collapsed');
858            });
859        };
860
861        /**
862         * Initialize sticky sidebar functionality
863         * Calculates appropriate top margin based on various header elements
864         */
865        const stickySidebar = function () {
866            // Only proceed if sticky elements exist
867            if (!$('.houzez_sticky').length) return;
868
869            // Initialize variables for margin calculation
870            let houzezStickyTop = 0;
871            let adminBarHeight = houzez.Core.config.wpadminbar_height;
872
873            // Get advanced search nav height if sticky
874            const advanced_search_nav = $('.advanced-search-nav');
875            let advanced_search_nav_height = 0;
876
877            if (advanced_search_nav.data('sticky') === 1) {
878                advanced_search_nav_height =
879                    advanced_search_nav.innerHeight() || 0;
880            }
881
882            // Get header nav height and sticky status
883            const header_nav = $('#header-section');
884            const houzez_nav_sticky_height = header_nav.innerHeight() || 0;
885            let only_nav_sticky = header_nav.data('sticky');
886
887            // Disable sticky nav on property detail pages with property navigation
888            if (
889                houzez.Core.config.property_detail_nav === 'yes' &&
890                houzez.Core.config.is_singular_property
891            ) {
892                only_nav_sticky = 0;
893            }
894
895            // Add header height to sticky top if header is sticky
896            if (only_nav_sticky === 1) {
897                houzezStickyTop = houzez_nav_sticky_height;
898            }
899
900            // Add admin bar height if present
901            if (adminBarHeight) {
902                houzezStickyTop += adminBarHeight;
903            }
904
905            // Get property navigation height if present
906            const listing_nav_area_height =
907                $('.property-navigation-wrap').innerHeight() || 0;
908
909            // Initialize theiaStickySidebar with calculated margins
910            $('.houzez_sticky').theiaStickySidebar({
911                additionalMarginTop:
912                    houzezStickyTop +
913                    advanced_search_nav_height +
914                    listing_nav_area_height,
915                minWidth: 768,
916                updateSidebarHeight: false,
917            });
918        };
919
920        const checkCollapse = function () {
921            var el = document.getElementById('search-expandable-collapse');
922
923            if (!el) return;
924
925            if (window.innerWidth < 992) {
926                // Mobile: chiuso
927                el.classList.remove('show');
928            } else {
929                // Desktop: aperto
930                el.classList.add('show');
931            }
932        };
933
934        /**
935         * One Page push state
936         */
937        const onePagePushState = function () {
938            $(
939                '.houzez-onepage-mode .header-main-wrap .main-nav li.nav-item a.nav-link'
940            ).on('click', function (e) {
941                var currentUrl = $(this).attr('href');
942                window.history.pushState({ houzezTheme: true }, '', currentUrl);
943            });
944        };
945
946        const houzezBannerSearchAutocomplete = function () {
947            const input = $('.search-icon input.form-control');
948            const dropdown = $('.auto-complete-banner');
949
950            // Only execute if auto-complete-banner exists
951            if (dropdown.length > 0 && input.length > 0) {
952                const rect = input[0].getBoundingClientRect();
953                const offset = 8; // distanza verticale extra
954
955                dropdown.css({
956                    position: 'absolute',
957                    top: `${window.scrollY + rect.bottom + offset}px`,
958                    left: `${window.scrollX + rect.left + rect.width / 2}px`,
959                    transform: 'translateX(-50%)',
960                    width: `${rect.width}px`,
961                    zIndex: '9999',
962                });
963            }
964        };
965
966        // Initialize core module
967        const init = function () {
968            $(document).ready(function () {
969                LoginRegisterTrigger();
970                listingViewSwitch();
971                initTooltips();
972                initPropertySorting();
973                initReviewsTab();
974                mobileToggleMenu();
975                //handlePageSpecificFunctions();
976                //initDynamicModals();
977                //is_mobile();
978                sortby_mobile();
979                gridCallToAction();
980                sidebarMenuToggle();
981                scrollToTop(); // Initialize scroll-to-top
982                stickySidebar();
983                houzezBannerSearchAutocomplete();
984            });
985
986            houzezLazyload();
987            onePagePushState();
988
989            document.addEventListener('DOMContentLoaded', checkCollapse);
990            // window.addEventListener("resize", checkCollapse);
991
992            // Initialize section height calculations
993            util.calculateSectionHeight();
994            $window.on('load', util.calculateSectionHeight);
995            $window.on('resize', util.calculateSectionHeight);
996            $window.on('scroll', houzezBannerSearchAutocomplete);
997            $window.on('resize', houzezBannerSearchAutocomplete);
998            $window.on('resize', checkCollapse);
999        };
1000
1001        // Public API
1002        return {
1003            init: init,
1004            config: config,
1005            util: util,
1006            initTooltips: initTooltips,
1007            hideAllTooltips: hideAllTooltips,
1008            initReviewsTab: initReviewsTab,
1009            listingViewSwitch: listingViewSwitch,
1010            is_mobile: is_mobile,
1011            //initDynamicModals: initDynamicModals,
1012            mobileToggleMenu: mobileToggleMenu,
1013            //handlePageSpecificFunctions: handlePageSpecificFunctions,
1014            gridCallToAction: gridCallToAction,
1015            scrollToTop: scrollToTop, // Expose scrollToTop
1016            stickySidebar: stickySidebar,
1017            reinitializeAjaxFunctions: reinitializeAjaxFunctions,
1018        };
1019    })();
1020
1021    houzez.PropertiesTabs = (function () {
1022        // Flag to track if tabs have been initialized
1023        let tabsInitialized = false;
1024
1025        /**
1026         * Initialize products tabs for Elementor
1027         */
1028        const propertiesTabsInit = function () {
1029            // Exit if already initialized to prevent duplicate event handlers
1030            if (tabsInitialized) {
1031                return;
1032            }
1033
1034            tabsInitialized = true;
1035            var alreadyProcessed = false;
1036
1037            $('.houzez-properties-tabs-js').each(function () {
1038                var $this = $(this);
1039                var $html_container = $this.find('.houzez-tab-content');
1040                var $products_cache = [];
1041                $this.find('ul.property-nav-tabs li').on('click', function (e) {
1042                    e.preventDefault();
1043                    var $this = $(this);
1044                    var settings = $this.data('json');
1045                    var data_index = $this.index();
1046
1047                    // Only check if processing, not if active
1048                    if (alreadyProcessed) {
1049                        return;
1050                    }
1051
1052                    // Remove active class from all tabs and add to the clicked tab
1053                    $this.siblings().find('a').removeClass('active');
1054                    $this.find('a').addClass('active');
1055
1056                    alreadyProcessed = true;
1057
1058                    if ($products_cache[data_index]) {
1059                        setTimeout(function () {
1060                            $html_container.html(
1061                                $products_cache[data_index].html
1062                            );
1063
1064                            houzez.Core.reinitializeAjaxFunctions();
1065
1066                            alreadyProcessed = false;
1067                        }, 300);
1068                        return;
1069                    }
1070
1071                    $.ajax({
1072                        url: houzez.Core.config.ajaxurl,
1073                        data: {
1074                            action: 'houzez_get_properties_tab_content',
1075                            settings: settings,
1076                        },
1077                        dataType: 'json',
1078                        method: 'POST',
1079                        beforeSend: function () {
1080                            $html_container
1081                                .empty()
1082                                .append(
1083                                    '<div id="houzez-map-loading">' +
1084                                        '<div class="mapPlaceholder">' +
1085                                        '<div class="loader-ripple spinner">' +
1086                                        '<div class="bounce1"></div>' +
1087                                        '<div class="bounce2"></div>' +
1088                                        '<div class="bounce3"></div>' +
1089                                        '</div>' +
1090                                        '</div>' +
1091                                        '</div>'
1092                                );
1093                        },
1094                        success: function (data) {
1095                            $products_cache[data_index] = data;
1096                            $html_container.html(data.html);
1097
1098                            houzez.Core.reinitializeAjaxFunctions();
1099                        },
1100                        error: function (xhr, status, error) {
1101                            var err = eval('(' + xhr.responseText + ')');
1102                            console.log(err.Message);
1103                        },
1104                        complete: function () {
1105                            alreadyProcessed = false;
1106                        },
1107                    });
1108                });
1109
1110                // Set first tab as active by default if none are active
1111                var $firstTab = $this.find(
1112                    'ul.property-nav-tabs li:first-child'
1113                );
1114                if (!$this.find('ul.property-nav-tabs li a.active').length) {
1115                    $firstTab.find('a').addClass('active');
1116                }
1117            });
1118        };
1119
1120        /**
1121         * Initialize the module
1122         */
1123        const init = function () {
1124            propertiesTabsInit();
1125        };
1126
1127        // Public API
1128        return {
1129            init: init,
1130            propertiesTabsInit: propertiesTabsInit,
1131        };
1132    })();
1133
1134    /**
1135     * Login Module
1136     * Handles authentication functions
1137     */
1138    houzez.Login = (function () {
1139        /**
1140         * Process login form submission
1141         */
1142        const processLogin = function (e) {
1143            // Prevent default form submission
1144            e.preventDefault();
1145
1146            // Get form data
1147            const $form = $(this);
1148            const $messages = $('#hz-login-messages');
1149            const config = houzez.Core.config;
1150
1151            // Check if already processing
1152            if ($form.hasClass('form-submitted')) {
1153                return;
1154            }
1155
1156            // Mark form as submitted
1157            $form.addClass('form-submitted');
1158
1159            // Show processing message
1160            houzez.Core.util.showSuccess($messages, config.login_loading);
1161
1162            // Send AJAX request
1163            $.ajax({
1164                type: 'POST',
1165                dataType: 'json',
1166                url: config.ajaxurl,
1167                data: $form.serialize(),
1168                beforeSend: function () {
1169                    $form.find('.btn-login').attr('disabled', true);
1170                    $form
1171                        .find('.btn-login .houzez-loader-js')
1172                        .addClass('loader-show');
1173                },
1174                success: function (response) {
1175                    if (response.success) {
1176                        houzez.Core.util.showSuccess($messages, response.msg);
1177
1178                        if (config.redirect_type === 'same_page') {
1179                            window.location.reload();
1180                        } else {
1181                            window.location.href = config.login_redirect;
1182                        }
1183                    } else {
1184                        houzez.Core.util.showError($messages, response.msg);
1185                    }
1186                },
1187                error: function (xhr, status, error) {
1188                    console.error('Login error: ' + error);
1189                    houzez.Core.util.showError(
1190                        $messages,
1191                        'Error processing login'
1192                    );
1193                },
1194                complete: function () {
1195                    $form.removeClass('form-submitted');
1196                    $form.find('.btn-login').attr('disabled', false);
1197                    $form
1198                        .find('.btn-login .houzez-loader-js')
1199                        .removeClass('loader-show');
1200                },
1201            });
1202
1203            return false;
1204        };
1205
1206        /**
1207         * Process registration form submission
1208         */
1209        const processRegister = function (e) {
1210            // Prevent default form submission
1211            e.preventDefault();
1212
1213            // Get form data
1214            const $form = $(this);
1215            const $messages = $('#hz-register-messages');
1216            const config = houzez.Core.config;
1217
1218            // Check if already processing
1219            if ($form.hasClass('form-submitted')) {
1220                return;
1221            }
1222
1223            // Mark form as submitted
1224            $form.addClass('form-submitted');
1225
1226            // Show processing message
1227            houzez.Core.util.showSuccess($messages, config.login_loading);
1228
1229            submitRegistrationForm();
1230
1231            function submitRegistrationForm() {
1232                // Send AJAX request
1233                $.ajax({
1234                    type: 'POST',
1235                    dataType: 'json',
1236                    url: config.ajaxurl,
1237                    data: $form.serialize(),
1238                    beforeSend: function () {
1239                        $form.find('.btn-register').attr('disabled', true);
1240                        $form
1241                            .find('.btn-register .houzez-loader-js')
1242                            .addClass('loader-show');
1243                    },
1244                    success: function (response) {
1245                        if (response.success) {
1246                            houzez.Core.util.showSuccess(
1247                                $messages,
1248                                response.msg
1249                            );
1250
1251                            setTimeout(function () {
1252                                $('#login-tab').tab('show');
1253                            }, 2000);
1254                        } else {
1255                            houzez.Core.util.showError($messages, response.msg);
1256                        }
1257                        // Reset captcha (supports both reCaptcha and Turnstile)
1258                        houzez.Core.util.resetCaptcha($form);
1259                    },
1260                    error: function (xhr, status, error) {
1261                        console.error('Registration error: ' + error);
1262                        houzez.Core.util.showError(
1263                            $messages,
1264                            'Error processing registration'
1265                        );
1266                    },
1267                    complete: function () {
1268                        $form.removeClass('form-submitted');
1269                        $form.find('.btn-register').attr('disabled', false);
1270                        $form
1271                            .find('.btn-register .houzez-loader-js')
1272                            .removeClass('loader-show');
1273                    },
1274                });
1275            }
1276        };
1277
1278        /**
1279         * Process password reset form
1280         */
1281        const processForgotPassword = function (e) {
1282            // Prevent default form submission
1283            e.preventDefault();
1284
1285            // Get form data
1286            const $form = $(this);
1287            const user_login = $('#user_login').val();
1288            const security = $('#fave_resetpassword_security').val();
1289            const $messages = $('#reset_pass_msg');
1290            const config = houzez.Core.config;
1291
1292            // Check if already processing
1293            if ($form.hasClass('form-submitted')) {
1294                return;
1295            }
1296
1297            // Mark form as submitted
1298            $form.addClass('form-submitted');
1299
1300            // Show processing message
1301            houzez.Core.util.showSuccess($messages, config.login_loading);
1302
1303            // Send AJAX request
1304            $.ajax({
1305                type: 'POST',
1306                dataType: 'json',
1307                url: config.ajaxurl,
1308                data: {
1309                    action: 'houzez_forgot_password',
1310                    user_login: user_login,
1311                    security: security,
1312                },
1313                beforeSend: function () {
1314                    $form.find('.btn-reset-password').attr('disabled', true);
1315                    $form
1316                        .find('.btn-reset-password .houzez-loader-js')
1317                        .addClass('loader-show');
1318                },
1319                success: function (response) {
1320                    if (response.success) {
1321                        houzez.Core.util.showSuccess($messages, response.msg);
1322                    } else {
1323                        houzez.Core.util.showError($messages, response.msg);
1324                    }
1325                },
1326                error: function (xhr, status, error) {
1327                    console.error('Reset password error: ' + error);
1328                    houzez.Core.util.showError(
1329                        $messages,
1330                        'Error processing request'
1331                    );
1332                },
1333                complete: function () {
1334                    $form.removeClass('form-submitted');
1335                    $form.find('.btn-reset-password').attr('disabled', false);
1336                    $form
1337                        .find('.btn-reset-password .houzez-loader-js')
1338                        .removeClass('loader-show');
1339                },
1340            });
1341
1342            return false;
1343        };
1344
1345        /**
1346         * Process reset password from reset password page
1347         */
1348        const processResetPassword = function () {
1349            if ($('#houzez_reset_password').length > 0) {
1350                $('#houzez_reset_password_form').on('submit', function (e) {
1351                    e.preventDefault();
1352                    var $this = $(this);
1353                    var rg_login = $('input[name="rp_login"]').val();
1354                    var rp_key = $('input[name="rp_key"]').val();
1355                    var pass1 = $('input[name="pass1"]').val();
1356                    var pass2 = $('input[name="pass2"]').val();
1357                    var security = $(
1358                        'input[name="resetpassword_security"]'
1359                    ).val();
1360                    var $messages = $('#reset_pass_msg_2');
1361
1362                    $.ajax({
1363                        type: 'post',
1364                        url: houzez.Core.config.ajaxurl,
1365                        dataType: 'json',
1366                        data: {
1367                            action: 'houzez_reset_password',
1368                            rq_login: rg_login,
1369                            password: pass1,
1370                            confirm_pass: pass2,
1371                            rp_key: rp_key,
1372                            security: security,
1373                        },
1374                        beforeSend: function () {
1375                            $this
1376                                .find('.houzez-loader-js')
1377                                .addClass('loader-show');
1378                        },
1379                        complete: function () {
1380                            $this
1381                                .find('.houzez-loader-js')
1382                                .removeClass('loader-show');
1383                        },
1384                        success: function (response) {
1385                            if (response.success) {
1386                                houzez.Core.util.showSuccess(
1387                                    $messages,
1388                                    response.msg
1389                                );
1390                                jQuery('#oldpass, #newpass, #confirmpass').val(
1391                                    ''
1392                                );
1393                            } else {
1394                                houzez.Core.util.showError(
1395                                    $messages,
1396                                    response.msg
1397                                );
1398                            }
1399                        },
1400                        error: function (errorThrown) {},
1401                    });
1402                });
1403            }
1404        };
1405
1406        // Initialize login module
1407        const init = function () {
1408            // Attach event handlers
1409            $('#houzez_login_form').on('submit', processLogin);
1410            $('#houzez_register_form').on('submit', processRegister);
1411            $('#houzez_forgot_password_form').on(
1412                'submit',
1413                processForgotPassword
1414            );
1415
1416            processResetPassword();
1417            // Handle popup login
1418            $('.hz-popup-login').on('click', function (e) {
1419                e.preventDefault();
1420                $('#login-register-form').modal('show');
1421                $('.login-form-tab').show();
1422                $('.register-form-tab').hide();
1423            });
1424
1425            // Handle popup register
1426            $('.hz-popup-register').on('click', function (e) {
1427                e.preventDefault();
1428                $('#login-register-form').modal('show');
1429                $('.register-form-tab').show();
1430                $('.login-form-tab').hide();
1431            });
1432        };
1433
1434        // Public API
1435        return {
1436            init: init,
1437            processLogin: processLogin,
1438            processRegister: processRegister,
1439            processForgotPassword: processForgotPassword,
1440            processResetPassword: processResetPassword,
1441        };
1442    })();
1443
1444    /**
1445     * Favorites Module
1446     * Handles property favorites functionality
1447     */
1448    houzez.Favorites = (function () {
1449        // Cache DOM selectors for better performance
1450        const $document = $(document);
1451        const $favoriteBtn = $('a.favorite-btn');
1452        const $favCount = $('span.frvt-count');
1453
1454        // Common selectors
1455        const selectors = {
1456            addFavorite: '.add-favorite-js',
1457            removeFavorite: '.remove_fav',
1458            loginRegisterForm: '#login-register-form',
1459            loginFormTab: '.login-form-tab',
1460            modalToggle1: '.modal-toggle-1.nav-link',
1461            registerFormTab: '.register-form-tab',
1462            modalToggle2: '.modal-toggle-2.nav-link',
1463        };
1464
1465        /**
1466         * Initialize adding a property to favorites
1467         */
1468        const initAddFavorite = function () {
1469            // Remove any existing handlers first to prevent duplicates
1470            $document.off('click.addFavorite', selectors.addFavorite);
1471
1472            // Add the handler with namespace for better management
1473            $document.on(
1474                'click.addFavorite',
1475                selectors.addFavorite,
1476                function (e) {
1477                    e.preventDefault();
1478                    e.stopPropagation();
1479
1480                    const $this = $(this);
1481                    const listID = $this.data('listid');
1482
1483                    add_to_favorite(
1484                        houzez.Core.config.ajaxurl,
1485                        listID,
1486                        $this,
1487                        houzez.Core.config.userID
1488                    );
1489                    return false;
1490                }
1491            );
1492        };
1493
1494        /**
1495         * Initialize removing a property from favorites
1496         */
1497        const initRemoveFavorite = function () {
1498            // Remove any existing handlers first to prevent duplicates
1499            $document.off('click.removeFavorite', selectors.removeFavorite);
1500
1501            // Add the handler with namespace for better management
1502            $document.on(
1503                'click.removeFavorite',
1504                selectors.removeFavorite,
1505                function (e) {
1506                    e.preventDefault();
1507                    e.stopPropagation();
1508
1509                    const $this = $(this);
1510                    const listID = $this.data('listid');
1511
1512                    add_to_favorite(
1513                        houzez.Core.config.ajaxurl,
1514                        listID,
1515                        $this,
1516                        houzez.Core.config.userID
1517                    );
1518
1519                    // If on the favorites page, remove the row
1520                    if ($this.parents('tr').length > 0) {
1521                        $this.parents('tr').remove();
1522                    }
1523
1524                    return false;
1525                }
1526            );
1527        };
1528
1529        /**
1530         * Initialize favorite dropdown link navigation
1531         * Fixes the issue where clicking the favorite link in the dropdown menu
1532         * doesn't navigate because the parent button has a modal trigger
1533         */
1534        const initFavoriteDropdownLink = function () {
1535            // Remove any existing handlers first to prevent duplicates
1536            $document.off('click.favoriteDropdown', '.dropdown-menu-favorites .favorite-btn.dropdown-item');
1537
1538            // Add the handler with namespace for better management
1539            $document.on(
1540                'click.favoriteDropdown',
1541                '.dropdown-menu-favorites .favorite-btn.dropdown-item',
1542                function (e) {
1543                    e.preventDefault();
1544                    e.stopPropagation();
1545
1546                    const $this = $(this);
1547                    const href = $this.attr('href');
1548
1549                    // Navigate to favorites page if href is valid
1550                    if (href && href !== '#' && href !== '') {
1551                        window.location.href = href;
1552                    }
1553
1554                    return false;
1555                }
1556            );
1557        };
1558
1559        /**
1560         * Show the login modal with login tab active
1561         */
1562        const showLoginModal = function () {
1563            $(selectors.loginRegisterForm).modal('show');
1564            $(selectors.loginFormTab).addClass('active show');
1565            $(selectors.modalToggle1).addClass('active');
1566            $(selectors.registerFormTab).removeClass('active show');
1567            $(selectors.modalToggle2).removeClass('active');
1568        };
1569
1570        /**
1571         * Update favorite button UI
1572         * @param {object} $element - jQuery object of the element
1573         * @param {boolean} isActive - Whether the favorite is active or not
1574         */
1575        const updateFavoriteUI = function ($element, isActive) {
1576            if (isActive) {
1577                $element.children('i').addClass('text-danger');
1578                if ($element.hasClass('add-favorite-js')) {
1579                    $element
1580                        .removeClass('add-favorite-js')
1581                        .addClass('remove_fav');
1582                }
1583                $element.html(
1584                    '<i class="houzez-icon icon-love-it text-danger"></i>'
1585                );
1586            } else {
1587                $element.children('i').removeClass('text-danger');
1588                if ($element.hasClass('remove_fav')) {
1589                    $element
1590                        .removeClass('remove_fav')
1591                        .addClass('add-favorite-js');
1592                }
1593                $element.html('<i class="houzez-icon icon-love-it"></i>');
1594            }
1595        };
1596
1597        /**
1598         * Add a property to favorites (works for both adding and removing)
1599         * @param {string} ajaxurl - Ajax URL
1600         * @param {number} listID - Property ID
1601         * @param {object} curnt - jQuery object of the clicked element
1602         * @param {number} userID - User ID
1603         */
1604        const add_to_favorite = function (ajaxurl, listID, curnt, userID) {
1605            const config = houzez.Core.config;
1606
1607            // Handle non-logged in users
1608            if (parseInt(userID, 10) === 0 || userID === undefined) {
1609                if (config.add_to_favorite_login_required != 0) {
1610                    // If login is required, show login modal
1611                    showLoginModal();
1612                } else {
1613                    // If login is not required, handle with cookies
1614                    add_to_favorite_with_cookies(curnt, listID);
1615                }
1616                return;
1617            }
1618
1619            // Handle logged in users with AJAX
1620            const $parents = curnt.parents('.item-wrap');
1621            const preview_loader = $parents.find('.preview_loader');
1622
1623            $.ajax({
1624                type: 'post',
1625                url: houzez.Core.config.ajaxurl,
1626                dataType: 'json',
1627                data: {
1628                    action: 'houzez_add_to_favorite',
1629                    listing_id: listID,
1630                },
1631                beforeSend: function () {
1632                    if (preview_loader.length) {
1633                        preview_loader
1634                            .empty()
1635                            .append(houzez.Core.util.getLoaderHtml());
1636                    } else {
1637                        curnt.html('<i class="houzez-loader-js"></i>');
1638                        curnt.find('.houzez-loader-js').addClass('loader-show');
1639                    }
1640                },
1641                complete: function () {
1642                    if (preview_loader.length) {
1643                        preview_loader.empty();
1644                    }
1645                },
1646                success: function (response) {
1647                    if (response.success) {
1648                        const isAdded = response.data.added;
1649                        updateFavoriteUI(curnt, isAdded);
1650                    }
1651                },
1652                error: function (xhr, status, error) {
1653                    console.log(error);
1654                },
1655            });
1656        };
1657
1658        /**
1659         * Update favorite count and URL
1660         * @param {Array} listings_favorite - Array of favorite listing IDs
1661         */
1662        const updateFavoriteCountAndUrl = function (listings_favorite) {
1663            $favCount.html('');
1664            $favCount.html(listings_favorite.length);
1665
1666            $favoriteBtn.attr(
1667                'href',
1668                houzez.Core.config.favorite_url +
1669                    '?ids=' +
1670                    listings_favorite.join(',')
1671            );
1672        };
1673
1674        /**
1675         * Add to favorite without login using cookies
1676         * @param {object} curnt - jQuery object of the clicked element
1677         * @param {number} listing_id - Listing ID
1678         */
1679        const add_to_favorite_with_cookies = function (curnt, listing_id) {
1680            let listings_favorite = houzez.Core.util.getCookie(
1681                'houzez_favorite_listings'
1682            );
1683
1684            if (listings_favorite && listings_favorite.length) {
1685                listings_favorite = listings_favorite.split(',');
1686            } else {
1687                listings_favorite = [];
1688            }
1689
1690            const index = listings_favorite.indexOf(listing_id.toString());
1691            const isAdding = index === -1;
1692
1693            if (isAdding) {
1694                // Add to favorites
1695                listings_favorite.push(listing_id.toString());
1696            } else {
1697                // Remove from favorites
1698                listings_favorite.splice(index, 1);
1699            }
1700
1701            // Save cookie
1702            houzez.Core.util.setCookie(
1703                'houzez_favorite_listings',
1704                listings_favorite.join(','),
1705                30
1706            );
1707
1708            // Update UI
1709            updateFavoriteCountAndUrl(listings_favorite);
1710            updateFavoriteUI(curnt, isAdding);
1711        };
1712
1713        /**
1714         * Check favorites for user (matches original implementation)
1715         * @param {number} userID - User ID
1716         */
1717        const check_favorites = function (userID) {
1718            if (parseInt(userID, 10) === 0 || userID === undefined) {
1719                let listings_favorite = houzez.Core.util.getCookie(
1720                    'houzez_favorite_listings'
1721                );
1722
1723                $favoriteBtn.attr(
1724                    'href',
1725                    houzez.Core.config.favorite_url +
1726                        '?ids=' +
1727                        listings_favorite
1728                );
1729
1730                if (listings_favorite && listings_favorite.length) {
1731                    listings_favorite = listings_favorite.split(',');
1732
1733                    if (listings_favorite.length) {
1734                        for (let i = 0; i < listings_favorite.length; i++) {
1735                            $(
1736                                `.add-favorite-js[data-listid="${listings_favorite[i]}"] i`
1737                            ).addClass('text-danger');
1738                            $(
1739                                `.add-favorite-js[data-listid="${listings_favorite[i]}"]`
1740                            ).addClass('remove-favorite');
1741                        }
1742
1743                        $favCount.html('');
1744                        $favCount.html(listings_favorite.length);
1745                    }
1746                } else {
1747                    listings_favorite = [];
1748                }
1749            }
1750            // No else block - no AJAX call for logged-in users in original co
1750de
1751        };
1752
1753        // Initialize favorites module
1754        const init = function () {
1755            initAddFavorite();
1756            initRemoveFavorite();
1757            initFavoriteDropdownLink();
1758            check_favorites(houzez.Core.config.userID);
1759        };
1760
1761        // Public API
1762        return {
1763            init: init,
1764            initAddFavorite: initAddFavorite,
1765            initRemoveFavorite: initRemoveFavorite,
1766            initFavoriteDropdownLink: initFavoriteDropdownLink,
1767            add_to_favorite: add_to_favorite,
1768            remove_from_favorite: add_to_favorite, // Use the same function for consistency
1769            check_favorites: check_favorites,
1770            showLoginModal: showLoginModal,
1771        };
1772    })();
1773
1774    /**
1775     * Compare Module
1776     * Handles property comparison functionality
1777     */
1778    houzez.Compare = (function () {
1779        // Limit for comparison items
1780        const limit_item_compare = 4;
1781
1782        /**
1783         * Add property to compare
1784         */
1785        function add_to_compare(
1786            compare_url,
1787            compare_add_icon,
1788            compare_remove_icon,
1789            add_compare_text,
1790            remove_compare_text,
1791            compare_limit,
1792            listings_compare,
1793            limit_item_compare
1794        ) {
1795            var storedData = localStorage.getItem('houzez_compare_listings');
1796            var listings_compare = storedData ? JSON.parse(storedData) : [];
1797
1798            $('a.compare-btn').attr(
1799                'href',
1800                compare_url +
1801                    '?ids=' +
1802                    listings_compare.map((item) => item.id).join(',')
1803            );
1804
1805            if (listings_compare.length > 0) {
1806                $('.compare-property-label').fadeIn(1000);
1807            }
1808
1809            if (listings_compare.length) {
1810                for (var i = 0; i < listings_compare.length; i++) {
1811                    $(
1812                        '.houzez_compare[data-listing_id="' +
1813                            listings_compare[i].id +
1814                            '"] i'
1815                    )
1816                        .removeClass('icon-add-circle')
1817                        .addClass('icon-subtract-circle');
1818                    $(
1819                        '.houzez_compare[data-listing_id="' +
1820                            listings_compare[i].id +
1821                            '"]'
1822                    ).attr('title', remove_compare_text);
1823                    $(
1824                        '.houzez_compare[data-listing_id="' +
1825                            listings_compare[i].id +
1826                            '"]'
1827                    )
1828                        .tooltip('hide')
1829                        .attr('data-original-title', remove_compare_text);
1830                }
1831                $('.compare-property-label')
1832                    .find('.compare-count')
1833                    .html(listings_compare.length);
1834            }
1835        }
1836
1837        /**
1838         * Handle compare button click
1839         */
1840        const handleCompareButtonClick = function (
1841            compare_url,
1842            compare_add_icon,
1843            compare_remove_icon,
1844            add_compare_text,
1845            remove_compare_text,
1846            compare_limit,
1847            limit_item_compare
1848        ) {
1849            $('.houzez_compare')
1850                .off('click')
1851                .on('click', function (e) {
1852                    e.preventDefault();
1853
1854                    var storedData = localStorage.getItem(
1855                        'houzez_compare_listings'
1856                    );
1857                    var listings_compare = storedData
1858                        ? JSON.parse(storedData)
1859                        : [];
1860
1861                    var listing_id = $(this).data('listing_id');
1862                    var index = listings_compare.findIndex(
1863                        (item) => item.id === listing_id.toString()
1864                    );
1865                    var image_div = $(this).parents('.item-wrap');
1866                    var thumb_url = image_div.find('img').attr('src');
1867
1868                    if (index == -1) {
1869                        if (listings_compare.length >= limit_item_compare) {
1870                            alert(compare_limit);
1871                        } else {
1872                            $('.compare-wrap').append(
1873                                '<div class="compare-item remove-' +
1874                                    listing_id +
1875                                    '"><a href="" class="remove-compare remove-icon" data-listing_id="' +
1876                                    listing_id +
1877                                    '"><i class="houzez-icon icon-remove-circle"></i></a><img class="img-fluid" src="' +
1878                                    thumb_url +
1879                                    '" width="200" height="150" alt="Thumb"></div>'
1880                            );
1881
1882                            listings_compare.push({
1883                                id: listing_id.toString(),
1884                                image: thumb_url,
1885                            });
1886                            localStorage.setItem(
1887                                'houzez_compare_listings',
1888                                JSON.stringify(listings_compare)
1889                            );
1890                            $(this).attr('title', remove_compare_text);
1891                            $(this)
1892                                .find('i')
1893                                .removeClass('icon-add-circle')
1894                                .addClass('icon-subtract-circle');
1895                            $('.compare-property-label')
1896                                .find('.compare-count')
1897                                .html(listings_compare.length);
1898                            $('a.compare-btn').attr(
1899                                'href',
1900                                compare_url +
1901                                    '?ids=' +
1902                                    listings_compare
1903                                        .map((item) => item.id)
1904                                        .join(',')
1905                            );
1906                            $('.compare-property-label').fadeIn(1000);
1907                            $(this).toggleClass('active');
1908                            $('.compare-property-active').addClass(
1909                                'compare-property-active-push-toleft'
1910                            );
1911                            $('#compare-property-panel').addClass(
1912                                'compare-property-panel-open'
1913                            );
1914                            $(this).tooltip('dispose').tooltip('show');
1915                            remove_from_compare(
1916                                listings_compare,
1917                                compare_add_icon,
1918                                compare_remove_icon,
1919                                add_compare_text,
1920                                remove_compare_text
1921                            );
1922                        }
1923                    } else {
1924                        $('div.remove-' + listing_id).remove();
1925                        $(this).attr('title', add_compare_text);
1926                        $(this)
1927                            .find('i')
1928                            .removeClass('icon-subtract-circle')
1929                            .addClass('icon-add-circle');
1930                        listings_compare.splice(index, 1);
1931                        localStorage.setItem(
1932                            'houzez_compare_listings',
1933                            JSON.stringify(listings_compare)
1934                        );
1935                        $('.compare-property-label')
1936                            .find('.compare-count')
1937                            .html(listings_compare.length);
1938                        $('a.compare-btn').attr(
1939                            'href',
1940                            compare_url +
1941                                '?ids=' +
1942                                listings_compare
1943                                    .map((item) => item.id)
1944                                    .join(',')
1945                        );
1946                        $(this).tooltip('dispose').tooltip('show');
1947
1948                        if (listings_compare.length > 0) {
1949                            $('.compare-property-label').fadeIn(1000);
1950                            $(this).toggleClass('active');
1951                            $('.compare-property-active').addClass(
1952                                'compare-property-active-push-toleft'
1953                            );
1954                            $('#compare-property-panel').addClass(
1955                                'compare-property-panel-open'
1956                            );
1957                        } else {
1958                            $('.compare-property-label').fadeOut(1000);
1959                        }
1960                    }
1961                    return false;
1962                });
1963        };
1964
1965        /**
1966         * Remove property from compare
1967         */
1968        function remove_from_compare(
1969            listings_compare,
1970            compare_add_icon,
1971            compare_remove_icon,
1972            add_compare_text,
1973            remove_compare_text
1974        ) {
1975            $('.remove-compare')
1976                .off('click')
1977                .on('click', function (e) {
1978                    e.preventDefault();
1979                    const config = houzez.Core.config;
1980                    var compare_url = config.compare_url;
1981                    var storedData = localStorage.getItem(
1982                        'houzez_compare_listings'
1983                    );
1984                    listings_compare = storedData ? JSON.parse(storedData) : [];
1985
1986                    var listing_id = $(this).data('listing_id');
1987                    var index = listings_compare.findIndex(
1988                        (item) => item.id === listing_id.toString()
1989                    );
1990
1991                    if (index !== -1) {
1992                        // Only proceed if the item was found
1993                        listings_compare.splice(index, 1);
1994                        localStorage.setItem(
1995                            'houzez_compare_listings',
1996                            JSON.stringify(listings_compare)
1997                        );
1998
1999                        $('.compare-property-label')
2000                            .find('.compare-count')
2001                            .html(listings_compare.length);
2002
2003                        // Update UI elements if they exist
2004                        var compareElement = $('.compare-' + listing_id);
2005                        if (compareElement.length) {
2006                            compareElement.attr('title', add_compare_text);
2007                            compareElement
2008                                .tooltip('hide')
2009                                .attr('data-original-title', add_compare_text);
2010                            compareElement
2011                                .find('i')
2012                                .removeClass('icon-subtract-circle')
2013                                .addClass('icon-add-circle');
2014                        }
2015
2016                        $(this).parents('.compare-item').remove();
2017
2018                        // Update the compare URL
2019                        $('a.compare-btn').attr(
2020                            'href',
2021                            compare_url +
2022                                '?ids=' +
2023                                listings_compare
2024                                    .map((item) => item.id)
2025                                    .join(',')
2026                        );
2027                    }
2028                });
2029        }
2030
2031        /**
2032         * Initialize the compare panel toggle functionality
2033         */
2034        const initComparePanel = function () {
2035            $('.show-compare-panel').on('click', function () {
2036                $(this).toggleClass('active');
2037                $('.compare-property-active').addClass(
2038                    'compare-property-active-push-toleft'
2039                );
2040                $('#compare-property-panel').addClass(
2041                    'compare-property-panel-open'
2042                );
2043            });
2044
2045            $('.close-compare-panel').on('click', function () {
2046                $(this).toggleClass('active');
2047                $('.compare-property-active').removeClass(
2048                    'compare-property-active-push-toleft'
2049                );
2050                $('#compare-property-panel').removeClass(
2051                    'compare-property-panel-open'
2052                );
2053            });
2054        };
2055
2056        /**
2057         * Update compare properties after AJAX operations
2058         * Used for refreshing comparison after content loads via AJAX
2059         */
2060        const compare_for_ajax = function () {
2061            const config = houzez.Core.config;
2062            var listings_compare = houzez.Core.util.getCookie(
2063                'houzez_compare_listings'
2064            );
2065
2066            add_to_compare(
2067                config.compare_url,
2068                config.compare_add_icon,
2069                config.compare_remove_icon,
2070                config.add_compare_text,
2071                config.remove_compare_text,
2072                config.compare_limit,
2073                listings_compare,
2074                limit_item_compare
2075            );
2076
2077            // Set up event handlers for compare buttons
2078            handleCompareButtonClick(
2079                config.compare_url,
2080                config.compare_add_icon,
2081                config.compare_remove_icon,
2082                config.add_compare_text,
2083                config.remove_compare_text,
2084                config.compare_limit,
2085                limit_item_compare
2086            );
2087
2088            remove_from_compare(
2089                listings_compare,
2090                config.compare_add_icon,
2091                config.compare_remove_icon,
2092                config.add_compare_text,
2093                config.remove_compare_text
2094            );
2095        };
2096
2097        /**
2098         * Render stored comparison properties in the compare panel
2099         */
2100        const renderComparePropertiesData = function () {
2101            let compare_listings = JSON.parse(
2102                localStorage.getItem('houzez_compare_listings')
2103            );
2104
2105            // Check if 'compare_listings' is not null and not empty before processing
2106            if (compare_listings && compare_listings.length > 0) {
2107                let properties_array = '';
2108                compare_listings.forEach(function (item) {
2109                    let img = item.image; // Image URL from the local storage array
2110                    let listingId = item.id; // Listing ID from the local storage array
2111
2112                    properties_array +=
2113                        '<div class="compare-item remove-' +
2114                        listingId +
2115                        '">' +
2116                        '<a href="#" class="remove-compare remove-icon" data-listing_id="' +
2117                        listingId +
2118                        '">' +
2119                        '<i class="houzez-icon icon-remove-circle"></i></a>' +
2120                        '<img class="img-fluid" src="' +
2121                        img +
2122                        '" width="200" height="150" alt="Thumb">' +
2123                        '</div>';
2124                });
2125
2126                $('.compare-wrap').html(properties_array);
2127            }
2128        };
2129
2130        /**
2131         * Initialize the compare module
2132         */
2133        const init = function () {
2134            initComparePanel();
2135            compare_for_ajax();
2136            renderComparePropertiesData();
2137
2138            // Call remove_from_compare again after items are rendered to ensure event handlers are attached
2139            const compare_listings =
2140                JSON.parse(localStorage.getItem('houzez_compare_listings')) ||
2141                [];
2142            remove_from_compare(
2143                compare_listings,
2144                houzez.Core.config.compare_add_icon,
2145                houzez.Core.config.compare_remove_icon,
2146                houzez.Core.config.add_compare_text,
2147                houzez.Core.config.remove_compare_text
2148            );
2149        };
2150
2151        // Public API
2152        return {
2153            init: init,
2154            add_to_compare: add_to_compare,
2155            remove_from_compare: remove_from_compare,
2156            compare_for_ajax: compare_for_ajax,
2157        };
2158    })();
2159
2160    /**
2161     * Sliders Module
2162     * Handles all slider and carousel functionality across the theme
2163     */
2164    houzez.Sliders = (function () {
2165        // Cache DOM selectors for better performance
2166        const $document = $(document);
2167        const $window = $(window);
2168
2169        // Common Slick slider settings
2170        const commonSettings = {
2171            rtl: houzez.Core.config.houzez_rtl,
2172            lazyLoad: 'ondemand',
2173            adaptiveHeight: true,
2174            speed: 500,
2175            arrows: true,
2176        };
2177
2178        const commonResponsiveSettings = [
2179            {
2180                breakpoint: 992,
2181                settings: {
2182                    slidesToShow: 2,
2183                    slidesToScroll: 1,
2184                },
2185            },
2186            {
2187                breakpoint: 768,
2188                settings: {
2189                    slidesToShow: 1,
2190                    slidesToScroll: 1,
2191                },
2192            },
2193        ];
2194
2195        const commonArrowButtons = {
2196            prevArrow: `<button type="button" class="slick-prev btn-primary-outlined">${houzez.Core.config.prev_text}</button>`,
2197            nextArrow: `<button type="button" class="slick-next btn-primary-outlined">${houzez.Core.config.next_text}</button>`,
2198        };
2199
2200        /**
2201         * Initialize slider with error handling
2202         * @param {jQuery} $element - jQuery element to initialize slider on
2203         * @param {Object} settings - Slick slider settings
2204         */
2205        const initSlider = function ($element, settings) {
2206            try {
2207                if (
2208                    $element &&
2209                    $element.length > 0 &&
2210                    typeof $.fn.slick === 'function'
2211                ) {
2212                    // Check if slick is already initialized
2213                    if ($element.hasClass('slick-initialized')) {
2214                        $element.slick('unslick');
2215                    }
2216
2217                    $element.slick({
2218                        ...commonSettings,
2219                        ...settings,
2220                    });
2221                } else if (!$element || $element.length === 0) {
2222                    console.warn('Slider element not found or empty');
2223                } else if (typeof $.fn.slick !== 'function') {
2224                    console.warn('Slick slider library not loaded');
2225                }
2226            } catch (error) {
2227                console.error('Error initializing slider:', error);
2228            }
2229        };
2230
2231        /**
2232         * Initialize custom carousel
2233         */
2234        const customCarousel = function () {
2235            const $customCarousel = $('.custom-carousel');
2236            if ($customCarousel.length > 0) {
2237                $customCarousel.each(function () {
2238                    const $this = $(this);
2239                    const token = $this.data('token');
2240                    const carouselSettings = $this.data('carousel');
2241
2242                    initSlider($('.custom-carousel-js-' + token), {
2243                        autoplay: carouselSettings.slide_auto === 'true',
2244                        autoplaySpeed:
2245                            parseInt(carouselSettings.auto_speed) || 3000,
2246                        infinite: carouselSettings.slide_infinite === 'true',
2247                        speed: 500,
2248                        slidesToShow:
2249                            parseInt(carouselSettings.slides_to_show) || 3,
2250                        slidesToScroll:
2251                            parseInt(carouselSettings.slides_to_scroll) || 1,
2252                        arrows: carouselSettings.navigation === 'true',
2253                        dots: carouselSettings.slide_dots === 'true',
2254                        appendArrows: '.custom-carousel-js-wrap-' + token,
2255                        prevArrow: $('.slick-prev-js-' + token),
2256                        nextArrow: $('.slick-next-js-' + token),
2257                        responsive: commonResponsiveSettings,
2258                    });
2259                });
2260            }
2261        };
2262
2263        /**
2264         * Initialize property detail gallery with optimized loading experience
2265         * Improved for faster display and better mobile performance
2266         */
2267        const propertyDetailGallery = function () {
2268            var property_detail_gallery = $('#property-gallery-js');
2269
2270            if (property_detail_gallery.length > 0) {
2271                // Check if already has a lightSlider instance
2272                // var existingInstance = property_detail_gallery.data('lightSlider');
2273                // if (existingInstance && typeof existingInstance.refresh === 'function') {
2274                //     // Refresh existing instance instead of recreating
2275                //     existingInstance.refresh();
2276                //     return;
2277                // }
2278
2279                if (!property_detail_gallery.hasClass('gallery-initialized')) {
2280                    try {
2281                        // Detect mobile for optimized timing
2282                        var isMobile = houzez.Core.is_mobile();
2283
2284                        // Get all images
2285                        var $images = property_detail_gallery.find(
2286                            '.houzez-gallery-img'
2287                        );
2288                        var totalImages = $images.length;
2289                        var initialLoadCount = Math.min(3, totalImages); // Load first 3 or less
2290                        var loadedCount = 0;
2291                        var galleryInitialized = false;
2292                        var sliderInitialized = false;
2293
2294                        // Function to initialize the slider and show gallery
2295                        var initializeSliderAndGallery = function () {
2296                            if (sliderInitialized) return;
2297                            sliderInitialized = true;
2298
2299                            // Small delay to let browser paint the visible element
2300                            setTimeout(function () {
2301                                // Show slider div before initializing (lightSlider needs it visible to measure)
2302                                property_detail_gallery.css('display', 'block');
2303
2304                                // Initialize slider on visible element
2305                                var slider =
2306                                    property_detail_gallery.lightSlider({
2307                                        rtl: houzez.Core.config.houzez_rtl,
2308                                        gallery: true,
2309                                        item: 1,
2310                                        thumbItem: 8,
2311                                        slideMargin: 0,
2312                                        speed: 500,
2313                                        adaptiveHeight: false,
2314                                        auto: false,
2315                                        loop: false,
2316                                        prevHtml:
2317                                            '<button type="button" class="slick-prev slick-arrow"></button>',
2318                                        nextHtml:
2319                                            '<button type="button" class="slick-next slick-arrow"></button>',
2320                                        onSliderLoad: function (el) {
2321                                            var _this = this;
2322
2323                                            // Store instance for later reference
2324                                            property_detail_gallery.data(
2325                                                'lightSlider',
2326                                                _this
2327                                            );
2328
2329                                            // Add class to parent to show slider and hide placeholder
2330                                            $(el)
2331                                                .closest('.top-gallery-section')
2332                                                .addClass('slider-loaded');
2333
2334                                            // Hide featured image placeholder now that gallery is ready
2335                                            // Delay slightly to ensure smooth LCP transition
2336                                            setTimeout(function () {
2337                                                var $placeholder = $(
2338                                                    '#gallery-featured-placeholder'
2339                                                );
2340                                                if ($placeholder.length > 0) {
2341                                                    $placeholder.addClass(
2342                                                        'hide-placeholder'
2343                                                    );
2344                                                }
2345                                            }, 100);
2346
2347                                            // Mark gallery as initialized
2348                                            property_detail_gallery.addClass(
2349                                                'gallery-initialized'
2350                                            );
2351                                            galleryInitialized = true;
2352
2353                                            // Load remaining images after gallery is visible
2354                                            if (
2355                                                totalImages > initialLoadCount
2356                                            ) {
2357                                                $images
2358                                                    .slice(initialLoadCount)
2359                                                    .each(function (index) {
2360                                                        var $img = $(this);
2361                                                        var lazySrc =
2362                                                            $img.data('lazy');
2363                                                        if (lazySrc) {
2364                                                            // Stagger loading to avoid blocking
2365                                                            setTimeout(
2366                                                                function () {
2367                                                                    var remainingImg =
2368                                                                        new Image();
2369                                                                    remainingImg.onload =
2370                                                                        function () {
2371                                                                            $img.attr(
2372                                                                                'src',
2373                                                                                lazySrc
2374                                                                            );
2375                                                                        };
2376                                                                    remainingImg.onerror =
2377                                                                        function () {
2378                                                                            // Set src anyway so browser can handle broken image
2379                                                                            $img.attr(
2380                                                                                'src',
2381                                                                                lazySrc
2382                                                                            );
2383                                                                        };
2384                                                                    remainingImg.src =
2385                                                                        lazySrc;
2386                                                                },
2387                                                                index * 50
2388                                                            );
2389                                                        }
2390                                                    });
2391                                            }
2392                                        },
2393                                    });
2394
2395                                // Store instance reference
2396                                property_detail_gallery.data(
2397                                    'lightSlider',
2398                                    slider
2399                                );
2400                            }, 100); // 10ms delay for browser paint
2401                        };
2402                        initializeSliderAndGallery();
2403                    } catch (error) {
2404                        // Error handling: Show gallery even if initialization fails
2405                        console.error('Gallery initialization error:', error);
2406                        property_detail_gallery.removeClass(
2407                            'gallery-preparing'
2408                        );
2409                        property_detail_gallery.addClass('gallery-initialized');
2410                    }
2411                }
2412            }
2413        };
2414
2415        /**
2416         * Initialize lightbox slider
2417         */
2418        const lightboxSlider = function () {
2419            const $lightboxSlider = $('#lightbox-slider-js');
2420            if ($lightboxSlider.length > 0) {
2421                initSlider($lightboxSlider, {
2422                    infinite: true,
2423                    slidesToShow: 1,
2424                    slidesToScroll: 1,
2425                    autoplay: false,
2426                    autoplaySpeed: 3000,
2427                    arrows: true,
2428                    dots: false,
2429                    responsive: [],
2430                });
2431
2432                $('.houzez-trigger-popup-slider-js').on('click', function (e) {
2433                    e.preventDefault();
2434
2435                    let slider_num = parseInt($(this).data('slider-no'));
2436                    setTimeout(function () {
2437                        $lightboxSlider.slick('slickGoTo', slider_num - 1);
2438                    }, 200);
2439                });
2440            }
2441        };
2442
2443        /**
2444         * Initialize variable width slider
2445         */
2446        const variableWidthSlider = function () {
2447            const $variableWidthSlider = $('.listing-slider-variable-width');
2448            if ($variableWidthSlider.length > 0) {
2449                initSlider($variableWidthSlider, {
2450                    infinite: true,
2451                    speed: 500,
2452                    slidesToShow: 1,
2453                    centerMode: true,
2454                    variableWidth: true,
2455                    arrows: true,
2456                    adaptiveHeight: true,
2457                });
2458
2459                // $('.property-detail-v5 #pills-gallery-tab').on('click', function () {
2460                //     if (!listing_slider_variable_width.hasClass('hz-slick-refreshed')) {
2461                //         setTimeout(function () {
2462                //             listing_slider_variable_width.slick('setPosition');
2463                //             listing_slider_variable_width.slick('refresh');
2464                //             listing_slider_variable_width.addClass(
2465                //                 'hz-slick-refreshed'
2466                //             );
2467                //         }, 0);
2468                //     }
2469                // });
2470            }
2471        };
2472
2473        /**
2474         * Initialize testimonials sliders
2475         */
2476        const testimonialsSliderV1 = function () {
2477            const $testimonialsSliderV1 = $('.testimonials-slider-wrap-v1');
2478            if ($testimonialsSliderV1.length > 0) {
2479                initSlider($testimonialsSliderV1, {
2480                    slidesToShow: 1,
2481                    slidesToScroll: 1,
2482                    autoplay: true,
2483                    autoplaySpeed: 4000,
2484                    dots: false,
2485                    arrows: true,
2486                    infinite: true,
2487                    speed: 500,
2488                    fade: false,
2489                    cssEase: 'ease',
2490                    slide: 'div',
2491                    appendArrows: '.testimonials-slider-wrap-v1-arrows',
2492                    prevArrow:
2493                        '<button type="button" class="slick-prev testimonials-slider-v1-prev"></button>',
2494                    nextArrow:
2495                        '<button type="button" class="slick-next testimonials-slider-v1-next"></button>',
2496                });
2497            }
2498        };
2499
2500        const testimonialsSliderV2 = function () {
2501            const $testimonialsSliderV2 = $('.testimonials-slider-wrap-v2');
2502            if ($testimonialsSliderV2.length > 0) {
2503                initSlider($testimonialsSliderV2, {
2504                    infinite: true,
2505                    speed: 500,
2506                    slidesToShow: 3,
2507                    arrows: true,
2508                    dots: true,
2509                    appendArrows: '.testimonials-slider-wrap-v2-arrows',
2510                    prevArrow:
2511                        '<button type="button" class="slick-prev testimonials-slider-v2-prev"></button>',
2512                    nextArrow:
2513                        '<button type="button" class="slick-next testimonials-slider-v2-next"></button>',
2514                    responsive: commonResponsiveSettings,
2515                });
2516            }
2517        };
2518
2519        const testimonialsSliderV3 = function () {
2520            const $testimonialsSliderV3 = $('.testimonials-slider-wrap-v3');
2521            if ($testimonialsSliderV3.length > 0) {
2522                initSlider($testimonialsSliderV3, {
2523                    infinite: true,
2524                    speed: 500,
2525                    slidesToShow: 1,
2526                    adaptiveHeight: true,
2527                    arrows: true,
2528                    dots: false,
2529                    appendArrows: '.testimonials-slider-wrap-v3-arrows',
2530                    prevArrow:
2531                        '<button type="button" class="slick-prev btn-primary testimonials-slider-v3-prev"></button>',
2532                    nextArrow:
2533                        '<button type="button" class="slick-next btn-primary testimonials-slider-v3-next"></button>',
2534                });
2535            }
2536        };
2537
2538        /**
2539         * Initialize agents carousel
2540         */
2541        const agentsCarousel = function () {
2542            const $agentsCarousel = $('.agents-carousel');
2543            if ($agentsCarousel.length > 0) {
2544                initSlider($agentsCarousel, {
2545                    slidesToShow: 4,
2546                    slidesToScroll: 1,
2547                    autoplay: true,
2548                    speed: 500,
2549                    autoplaySpeed: 3000,
2550                    arrows: true,
2551                    dots: false,
2552                    responsive: commonResponsiveSettings,
2553                });
2554            }
2555        };
2556
2557        /**
2558         * Initialize partners carousel
2559         */
2560        const partnersCarousel = function () {
2561            const $partnersCarousel = $('.partners-slider-wrap');
2562            if ($partnersCarousel.length > 0) {
2563                initSlider($partnersCarousel, {
2564                    slidesToShow: 4,
2565                    slidesToScroll: 1,
2566                    autoplay: true,
2567                    autoplaySpeed: 3000,
2568                    arrows: true,
2569                    dots: true,
2570                    appendArrows: '.partners-module-slider',
2571                    prevArrow: $('.partner-prev-js'),
2572                    nextArrow: $('.partner-next-js'),
2573                    responsive: commonResponsiveSettings,
2574                });
2575            }
2576        };
2577
2578        /**
2579         * Initialize partners carousel
2580         */
2581        const splashSlider = function () {
2582            const $splashSlider = $('.splash-slider-wrap');
2583            if ($splashSlider.length > 0) {
2584                initSlider($splashSlider, {
2585                    lazyLoad: 'ondemand',
2586                    slidesToShow: 1,
2587                    slidesToScroll: 1,
2588                    autoplay: true,
2589                    infinite: true,
2590                    autoplaySpeed: 3000,
2591                    arrows: false,
2592                    dots: false,
2593                });
2594            }
2595        };
2596
2597        const propertyScheduleTourDayFormSlide = function () {
2598            const $propertyScheduleTourDayFormSlide = $(
2599                '.property-schedule-tour-day-form-slide'
2600            );
2601            if ($propertyScheduleTourDayFormSlide.length > 0) {
2602                initSlider($propertyScheduleTourDayFormSlide, {
2603                    infinite: true,
2604                    lazyLoad: 'ondemand',
2605                    speed: 500,
2606                    slidesToShow: 4,
2607                    slidesToScroll: 4,
2608                    arrows: true,
2609                    adaptiveHeight: false,
2610                    dots: false,
2611                    prevArrow:
2612                        '<div class="tour-day-form-slide-arrow prev" role="button" aria-label="Previous dates"><i class="houzez-icon icon-arrow-left-1" aria-hidden="true"></i></div>',
2613                    nextArrow:
2614                        '<div class="tour-day-form-slide-arrow next" role="button" aria-label="Next dates"><i class="houzez-icon icon-arrow-right-1" aria-hidden="true"></i></div>',
2615                    responsive: [
2616                        {
2617                            breakpoint: 992,
2618                            settings: {
2619                                slidesToShow: 4,
2620                                slidesToScroll: 4,
2621                            },
2622                        },
2623                        {
2624                            breakpoint: 769,
2625                            settings: {
2626                                slidesToShow: 3,
2627                                slidesToScroll: 3,
2628                            },
2629                        },
2630                        {
2631                            breakpoint: 480,
2632                            settings: {
2633                                slidesToShow: 2,
2634                                slidesToScroll: 2,
2635                            },
2636                        },
2637                    ],
2638                });
2639            }
2640        };
2641
2642        /**
2643         * Initialize featured property widget
2644         */
2645        const featuredPropertyWidget = function () {
2646            const $featuredPropertyWidget = $(
2647                '.widget-featured-property-slider'
2648            );
2649            if ($featuredPropertyWidget.length > 0) {
2650                initSlider($featuredPropertyWidget, {
2651                    slidesToShow: 1,
2652                    slidesToScroll: 1,
2653                    autoplay: true,
2654                    autoplaySpeed: 3000,
2655                    arrows: true,
2656                    dots: false,
2657                    responsive: [],
2658                });
2659            }
2660        };
2661
2662        /**
2663         * Initialize property banner slider
2664         */
2665        const propertyBannerSlider = function () {
2666            const $propertyBannerSlider = $('.property-banner-slider');
2667            if ($propertyBannerSlider.length > 0) {
2668                initSlider($propertyBannerSlider, {
2669                    slidesToShow: 1,
2670                    slidesToScroll: 1,
2671                    autoplay: true,
2672                    autoplaySpeed: 4000,
2673                    arrows: true,
2674                    dots: false,
2675                    responsive: [],
2676                });
2677            }
2678        };
2679
2680        /**
2681         * Initialize splash page slider
2682         */
2683        const splashPageSlider = function () {
2684            const $splashPageSlider = $('.splash-slider');
2685            if ($splashPageSlider.length > 0) {
2686                initSlider($splashPageSlider, {
2687                    slidesToShow: 1,
2688                    slidesToScroll: 1,
2689                    autoplay: true,
2690                    autoplaySpeed: 3000,
2691                    arrows: false,
2692                    dots: true,
2693                    responsive: [],
2694                });
2695            }
2696        };
2697
2698        /**
2699         * Initialize grid image gallery for property listings
2700         */
2701        const gridImageGallery = function () {
2702            var is_listing_gallery_slider = false;
2703            if (houzez_vars.disable_property_gallery == 1) {
2704                let gallery_behaviour = houzez_vars.grid_gallery_behaviour;
2705
2706                $('.hz-item-gallery-js').each(function () {
2707                    let $this = $(this);
2708
2709                    if (
2710                        !$this.hasClass('houzez-gallery-loaded') &&
2711                        $this.data('images')
2712                    ) {
2713                        // First check if URL was previously stored
2714                        var href = $this.data('property-url');
2715                        if (!href) {
2716                            // Try to find the link
2717                            href = $this
2718                                .find('a.listing-featured-thumb')
2719                                .attr('href');
2720                            // Store it for future re-initializations
2721                            if (href) {
2722                                $this.data('property-url', href);
2723                            }
2724                        }
2725
2726                        // Similarly for link_target
2727                        var link_target = $this.data('property-target');
2728                        if (!link_target) {
2729                            link_target = $this
2730                                .find('a.listing-featured-thumb')
2731                                .attr('target');
2732                            if (link_target) {
2733                                $this.data('property-target', link_target);
2734                            }
2735                        }
2736                        var images = $this.data('images');
2737
2738                        // Create document fragment for better performance
2739                        var fragment = document.createDocumentFragment();
2740                        var galleryWrap = document.createElement('div');
2741                        galleryWrap.className =
2742                            'listing-gallery-wrap ' + gallery_behaviour;
2743
2744                        var carouselDiv = document.createElement('div');
2745                        carouselDiv.className = 'houzez-listing-carousel';
2746
2747                        // Clear existing content before adding new elements
2748                        carouselDiv.innerHTML = '';
2749
2750                        images.forEach((image) => {
2751                            const itemDiv = document.createElement('div');
2752                            itemDiv.className = 'slide-img';
2753
2754                            const anchor = document.createElement('a');
2755                            anchor.className = 'hover-effect'; // Added wf class from your edit
2756                            anchor.href = href;
2757                            anchor.target = link_target;
2758                            anchor.setAttribute('role', 'link');
2759
2760                            const img = document.createElement('img');
2761                            img.src = image.image;
2762                            img.alt = image.alt || ''; // Ensure alt is not undefined
2763                            img.width = image.width;
2764                            img.height = image.height;
2765                            img.className = 'img-fluid';
2766
2767                            // Add srcset and sizes attributes if available
2768                            if (image.srcset) {
2769                                img.srcset = image.srcset;
2770                            }
2771                            if (image.sizes) {
2772                                img.sizes = image.sizes;
2773                            }
2774
2775                            anchor.appendChild(img);
2776                            itemDiv.appendChild(anchor);
2777                            carouselDiv.appendChild(itemDiv);
2778                        });
2779
2780                        galleryWrap.appendChild(carouselDiv);
2781                        fragment.appendChild(galleryWrap);
2782
2783                        $this.find('.listing-image-wrap').html(fragment);
2784
2785                        $('.item-wrap-v6 a').removeClass('hover-effect');
2786
2787                        var listing_slider = $this.find(
2788                            '.houzez-listing-carousel'
2789                        );
2790
2791                        listing_slider.slick({
2792                            rtl: houzez.Core.config.houzez_rtl,
2793                            autoplay: false,
2794                            lazyLoad: 'ondemand',
2795                            infinite: false,
2796                            speed: 500,
2797                            slidesToShow: 1,
2798                            arrows: true,
2799                            prevArrow:
2800                                '<button type="button" class="slick-prev slick-arrow"></button>',
2801                            nextArrow:
2802                                '<button type="button" class="slick-next slick-arrow"></button>',
2803                            adaptiveHeight: true,
2804                        });
2805
2806                        is_listing_gallery_slider = true;
2807                        $this.addClass('houzez-gallery-loaded');
2808                    }
2809                });
2810            }
2811        };
2812
2813        /**
2814         * Initialize the module
2815         */
2816        const init = function () {
2817            // Initialize functions that need DOM ready
2818            $(document).ready(function () {
2819                // Initialize grid gallery on document ready for better reliability
2820                gridImageGallery();
2821
2822                // Initialize other components that work well with DOM ready
2823                customCarousel();
2824                partnersCarousel();
2825                featuredPropertyWidget();
2826            });
2827
2828            // Initialize functions that need all resources loaded
2829            $(window).on('load', function () {
2830                propertyDetailGallery();
2831                lightboxSlider();
2832                variableWidthSlider();
2833                testimonialsSliderV1();
2834                testimonialsSliderV2();
2835                testimonialsSliderV3();
2836                agentsCarousel();
2837                customCarousel();
2838                partnersCarousel();
2839                propertyBannerSlider();
2840                featuredPropertyWidget();
2841                splashPageSlider();
2842                gridImageGallery(); // Keep here too as fallback
2843                splashSlider();
2844                propertyScheduleTourDayFormSlide();
2845            });
2846
2847            // Fallback initialization for cached pages
2848            setTimeout(function () {
2849                // Check if galleries exist but aren't initialized
2850                if (
2851                    $('.hz-item-gallery-js').length > 0 &&
2852                    $('.hz-item-gallery-js:not(.houzez-gallery-loaded)')
2853                        .length > 0
2854                ) {
2855                    gridImageGallery();
2856                }
2857            }, 500);
2858        };
2859
2860        // Public API
2861        return {
2862            init: init,
2863            initSlider: initSlider,
2864            customCarousel: customCarousel,
2865            propertyBannerSlider: propertyBannerSlider,
2866            propertyDetailGallery: propertyDetailGallery,
2867            lightboxSlider: lightboxSlider,
2868            variableWidthSlider: variableWidthSlider,
2869            testimonialsSliderV1: testimonialsSliderV1,
2870            agentsCarousel: agentsCarousel,
2871            partnersCarousel: partnersCarousel,
2872            //partnersSlider: partnersSlider,
2873            featuredPropertyWidget: featuredPropertyWidget,
2874            splashPageSlider: splashPageSlider,
2875            gridImageGallery: gridImageGallery,
2876            testimonialsSliderV2: testimonialsSliderV2,
2877            testimonialsSliderV3: testimonialsSliderV3,
2878            propertyScheduleTourDayFormSlide: propertyScheduleTourDayFormSlide,
2879        };
2880    })();
2881
2882    /**
2883     * Pagination Module
2884     * Handles AJAX pagination and infinite loading for property listings
2885     */
2886    houzez.Pagination = (function () {
2887        // Variables for tracking pagination state
2888        let moduleLoading = false;
2889        let listingsLoading = false;
2890        let moduleObserver = null;
2891        let listingsObserver = null;
2892        let loadMoreButton = $('#properties_module_section .fave-load-more a');
2893        let paginationType =
2894            loadMoreButton.data('pagination_type') || 'loadmore';
2895
2896        /**
2897         * Load more properties with AJAX
2898         * Used in property modules section with load more button
2899         * @param {Event} e - Click event
2900         */
2901        const loadMoreModuleProperties = function (e) {
2902            if (e) e.preventDefault();
2903            if (moduleLoading) {
2904                return;
2905            }
2906
2907            const $this = $(this);
2908            const $wrap = $this
2909                .closest('#properties_module_section')
2910                .find('#module_properties');
2911
2912            // Initialize the data object with the action
2913            let data = {
2914                action: 'houzez_loadmore_properties',
2915            };
2916
2917            // Dynamically collect all data attributes
2918            const allData = $this.data();
2919
2920            // Merge all data attributes into the data object
2921            $.each(allData, function (key, value) {
2922                data[key] = value;
2923            });
2924
2925            moduleLoading = true;
2926            $.ajax({
2927                type: 'POST',
2928                url: houzez.Core.config.ajaxurl,
2929                dataType: 'json',
2930                data: data,
2931                beforeSend: function () {
2932                    $this.find('.houzez-loader-js').addClass('loader-show');
2933                },
2934                complete: function () {
2935                    $this.find('.houzez-loader-js').removeClass('loader-show');
2936                    moduleLoading = false;
2937                },
2938                success: function (data) {
2939                    if (data.html == 'no_result') {
2940                        $this
2941                            .closest('#properties_module_section')
2942                            .find('.fave-load-more')
2943                            .fadeOut('fast')
2944                            .remove();
2945                        return;
2946                    }
2947
2948                    $wrap.append(data.html);
2949                    $this.data('paged', parseInt($this.data('paged')) + 1);
2950                    $this.find('i').remove();
2951
2952                    if (!data.has_more_posts) {
2953                        $this
2954                            .closest('#properties_module_section')
2955                            .find('.fave-load-more')
2956                            .fadeOut('fast')
2957                            .remove();
2958                    } else if (paginationType === 'infinite_scroll') {
2959                        // Re-observe the button after new content is loaded
2960                        observeModuleLoadMoreButton();
2961                    }
2962
2963                    // Use the Core function instead of local one
2964                    houzez.Core.reinitializeAjaxFunctions();
2965                },
2966                error: function (xhr, status, error) {
2967                    moduleLoading = false;
2968                    console.error('AJAX error:', error);
2969                },
2970            });
2971        };
2972
2973        const observeModuleLoadMoreButton = function () {
2974            if (moduleObserver) {
2975                moduleObserver.disconnect();
2976            }
2977
2978            const loadMoreButton = document.querySelector(
2979                '#properties_module_section .fave-load-more a'
2980            );
2981
2982            if (!loadMoreButton) return;
2983
2984            moduleObserver = new IntersectionObserver(
2985                (entries) => {
2986                    entries.forEach((entry) => {
2987                        if (entry.isIntersecting && !moduleLoading) {
2988                            loadMoreModuleProperties.call(entry.target);
2989                        }
2990                    });
2991                },
2992                {
2993                    root: null,
2994                    rootMargin: '0px 0px 300px 0px', // Increased bottom margin for earlier detection
2995                    threshold: 0.1,
2996                }
2997            );
2998
2999            moduleObserver.observe(loadMoreButton);
3000        };
3001
3002        /**
3003         * Load more listings for main listings view
3004         */
3005        const loadMoreListings = function () {
3006            if (listingsLoading || this.noMoreListings) {
3007                return;
3008            }
3009
3010            listingsLoading = true;
3011            const $loadMoreBtn = $('.houzez-infinite-load');
3012            const nextPageUrl = $loadMoreBtn.attr('href');
3013
3014            $.ajax({
3015                url: nextPageUrl,
3016                type: 'GET',
3017                dataType: 'html',
3018                beforeSend: function () {
3019                    $loadMoreBtn
3020                        .find('.houzez-loader-js')
3021                        .addClass('loader-show');
3022                },
3023                complete: function () {
3024                    $loadMoreBtn
3025                        .find('.houzez-loader-js')
3026                        .removeClass('loader-show');
3027                },
3028                success: function (response) {
3029                    const $html = $(response);
3030                    const $listingView = $('.listing-view');
3031                    const $paginationContainer = $('#fave-pagination-loadmore');
3032                    const $newListings = $html.find(
3033                        '.listing-view .item-listing-wrap'
3034                    );
3035
3036                    if ($newListings.length > 0) {
3037                        $listingView.append($newListings);
3038                        this.currentPage++;
3039
3040                        const nextPageLink = $html
3041                            .find('.houzez-infinite-load')
3042                            .attr('href');
3043
3044                        if (
3045                            nextPageLink &&
3046                            this.currentPage * this.perPage < this.totalListings
3047                        ) {
3048                            $loadMoreBtn.attr('href', nextPageLink);
3049                            $loadMoreBtn.attr(
3050                                'data-page',
3051                                this.currentPage + 1
3052                            );
3053                            updateBrowserUrl(nextPageUrl);
3054                            $loadMoreBtn.show(); // Ensure button is visible
3055                        } else {
3056                            this.noMoreListings = true;
3057                            $paginationContainer.html(
3058                                '<p class="no-more-listings">' +
3059                                    houzez_vars.listings_not_found +
3060                                    '</p>'
3061                            );
3062                            $loadMoreBtn.hide(); // Hide button when no more listings
3063                        }
3064
3065                        // Use the Core function instead of local one
3066                        houzez.Core.reinitializeAjaxFunctions();
3067                        if ($('.houzez-parallax').length > 0) {
3068                            houzez.Parallax.init();
3069                        }
3070                    } else {
3071                        this.noMoreListings = true;
3072                        $paginationContainer.html(
3073                            '<p class="no-more-listings">' +
3074                                houzez_vars.listings_not_found +
3075                                '</p>'
3076                        );
3077                        $loadMoreBtn.hide(); // Hide button when no more listings
3078                    }
3079
3080                    listingsLoading = false;
3081                }.bind(this),
3082                error: function (xhr, status, error) {
3083                    listingsLoading = false;
3084                },
3085            });
3086        };
3087
3088        /**
3089         * Update browser URL for history state
3090         */
3091        const updateBrowserUrl = function (url) {
3092            if (history.pushState) {
3093                const newUrl = new URL(url);
3094                window.history.pushState(
3095                    { path: newUrl.href },
3096                    '',
3097                    newUrl.href
3098                );
3099            }
3100        };
3101
3102        /**
3103         * Initialize the module pagination functionality
3104         */
3105        const initPropertiesModulePagination = function () {
3106            const properties_module =
3107                $('#properties_module_section').length > 0;
3108
3109            if (!properties_module) {
3110                return;
3111            }
3112
3113            // Load More button click event
3114            $('body').on(
3115                'click',
3116                '#properties_module_section .fave-load-more a',
3117                loadMoreModuleProperties
3118            );
3119
3120            // Initialize Infinite Scroll if that's the pagination type
3121            if (paginationType === 'infinite_scroll') {
3122                observeModuleLoadMoreButton();
3123            }
3124        };
3125
3126        /**
3127         * Handle main listings infinite load pagination
3128         */
3129        const initListingsInfiniteScroll = function () {
3130            const $listingView = $('.listing-view');
3131            const $loadMoreBtn = $('.houzez-infinite-load');
3132
3133            // Skip if we're in properties module context or there's no listing view
3134            const inModuleContext = $('#properties_module_section').length > 0;
3135
3136            if (inModuleContext || !$listingView.length || !$loadMoreBtn.length)
3137                return;
3138
3139            // Set up context with pagination state
3140            const context = {
3141                currentPage: 1,
3142                noMoreListings: false,
3143                totalListings: parseInt($loadMoreBtn.data('total'), 10) || 0,
3144                perPage: parseInt($loadMoreBtn.data('per-page'), 10) || 10,
3145            };
3146
3147            const pagi_type = $loadMoreBtn.data('pagi-type') || '_number';
3148
3149            // Bind loadMoreListings to the context
3150            const boundLoadMoreListings = loadMoreListings.bind(context);
3151
3152            // Check if we need to show the Load More button initially
3153            if (context.totalListings <= context.perPage) {
3154                $loadMoreBtn.hide();
3155            }
3156
3157            // Set up infinite scroll
3158            if (pagi_type == '_infinite' && $loadMoreBtn.length > 0) {
3159                if (listingsObserver) {
3160                    listingsObserver.disconnect();
3161                }
3162
3163                listingsObserver = new IntersectionObserver(
3164                    (entries) => {
3165                        entries.forEach((entry) => {
3166                            if (
3167                                entry.isIntersecting &&
3168                                !listingsLoading &&
3169                                !context.noMoreListings
3170                            ) {
3171                                boundLoadMoreListings();
3172                            }
3173                        });
3174                    },
3175                    {
3176                        root: null,
3177                        rootMargin: '0px 0px 300px 0px', // Increased bottom margin
3178                        threshold: 0.1,
3179                    }
3180                );
3181
3182                // Start observing the load more button
3183                listingsObserver.observe($loadMoreBtn[0]);
3184            }
3185
3186            // Keep the click event for browsers that don't support IntersectionObserver
3187            $loadMoreBtn.on('click', function (e) {
3188                e.preventDefault();
3189                if (!listingsLoading && !context.noMoreListings) {
3190                    boundLoadMoreListings();
3191                }
3192            });
3193
3194            // Handle browser back/forward buttons
3195            $(window).on('popstate', function (e) {
3196                if (e.originalEvent.state !== null) {
3197                    window.location.reload();
3198                }
3199            });
3200        };
3201
3202        /**
3203         * Initialize the module
3204         */
3205        const init = function () {
3206            initPropertiesModulePagination();
3207            initListingsInfiniteScroll();
3208        };
3209
3210        // Public API
3211        return {
3212            init: init,
3213            loadMoreModuleProperties: loadMoreModuleProperties,
3214            loadMoreListings: loadMoreListings,
3215        };
3216    })();
3217
3218    /**
3219     * Mortgage Module
3220     * Handles mortgage calculator functionality
3221     */
3222    houzez.Mortgage = (function () {
3223        /**
3224         * Calculation handler for mortgage calculator section
3225         */
3226        const mortgageCalculation = function () {
3227            const homePrice = houzez.Core.util.parseNumberInput('#homePrice');
3228            const downPaymentPercentage = houzez.Core.util.parseNumberInput(
3229                '#downPaymentPercentage'
3230            );
3231            const annualInterestRate = houzez.Core.util.parseNumberInput(
3232                '#annualInterestRate'
3233            );
3234            const loanTermInYears =
3235                houzez.Core.util.parseNumberInput('#loanTermInYears');
3236            const annualPropertyTaxRate = houzez.Core.util.parseNumberInput(
3237                '#annualPropertyTaxRate'
3238            );
3239            const annualHomeInsurance = houzez.Core.util.parseNumberInput(
3240                '#annualHomeInsurance'
3241            );
3242            const monthlyHOAFees =
3243                houzez.Core.util.parseNumberInput('#monthlyHOAFees');
3244            const pmi = houzez.Core.util.parseNumberInput('#pmi');
3245
3246            const downPayment = homePrice * (downPaymentPercentage / 100);
3247            const principal = homePrice - downPayment;
3248            const monthlyPayment = houzez.Core.util.calculateMonthlyPayment(
3249                principal,
3250                annualInterestRate,
3251                loanTermInYears
3252            );
3253            const monthlyPropertyTax =
3254                (homePrice * (annualPropertyTaxRate / 100)) / 12;
3255            const monthlyHomeInsurance = annualHomeInsurance / 12;
3256
3257            const pmiRequired = downPayment / homePrice < 0.2;
3258            const monthlyPMI = pmiRequired ? (principal * (pmi / 100)) / 12 : 0;
3259
3260            const totalMonthlyPayment =
3261                monthlyPayment +
3262                monthlyPropertyTax +
3263                monthlyHomeInsurance +
3264                monthlyHOAFees +
3265                monthlyPMI;
3266
3267            const loanAmount = homePrice - downPayment;
3268
3269            // Use numberFormat from Core utility
3270            const numberFormat = houzez.Core.util.numberFormat;
3271
3272            const formattedDownPayment = numberFormat(downPayment, true);
3273            const formattedLoanAmount = numberFormat(loanAmount, true);
3274            const formattedMonthlyPayment = numberFormat(monthlyPayment, true);
3275            const formattedPropertyTax = numberFormat(monthlyPropertyTax, true);
3276            const formattedHomeInsurance = numberFormat(
3277                monthlyHomeInsurance,
3278                true
3279            );
3280            const formattedPMI = pmiRequired
3281                ? numberFormat(monthlyPMI, true)
3282                : '';
3283            const formattedHOAFees = numberFormat(monthlyHOAFees, true);
3284            const formattedTotalMonthlyPayment = numberFormat(
3285                totalMonthlyPayment,
3286                true
3287            );
3288
3289            // Update UI elements with results
3290            $('#downPaymentResult').html(
3291                houzez.Core.util.currencyFormat(formattedDownPayment)
3292            );
3293            $('#loadAmountResult').html(
3294                houzez.Core.util.currencyFormat(formattedLoanAmount)
3295            );
3296            $('#monthlyMortgagePaymentResult').html(
3297                houzez.Core.util.currencyFormat(formattedMonthlyPayment)
3298            );
3299            $('#monthlyPropertyTaxResult').html(
3300                houzez.Core.util.currencyFormat(formattedPropertyTax)
3301            );
3302            $('#monthlyHomeInsuranceResult').html(
3303                houzez.Core.util.currencyFormat(formattedHomeInsurance)
3304            );
3305
3306            if (pmiRequired) {
3307                $('.rslt-pmi').show();
3308                $('#monthlyPMIResult').html(
3309                    houzez.Core.util.currencyFormat(formattedPMI)
3310                );
3311            } else {
3312                $('.rslt-pmi').hide();
3313            }
3314
3315            $('#monthlyHOAResult').html(
3316                houzez.Core.util.currencyFormat(formattedHOAFees)
3317            );
3318            $('#m_monthly_val').html(
3319                houzez.Core.util.currencyFormat(formattedTotalMonthlyPayment)
3320            );
3321
3322            // Update chart data for visualization
3323            const chartData = [
3324                {
3325                    label: 'Monthly Mortgage Payment',
3326                    value: monthlyPayment,
3327                    color: '#ff6384',
3328                },
3329                {
3330                    label: 'Property Tax',
3331                    value: monthlyPropertyTax,
3332                    color: '#36a2eb',
3333                },
3334                {
3335                    label: 'Home Insurance',
3336                    value: monthlyHomeInsurance,
3337                    color: '#ffce56',
3338                },
3339                {
3340                    label: 'HOA',
3341                    value: monthlyHOAFees,
3342                    color: '#c2d500',
3343                },
3344            ];
3345
3346            if (pmiRequired) {
3347                chartData.push({
3348                    label: 'PMI',
3349                    value: monthlyPMI,
3350                    color: '#4bc0c0',
3351                });
3352            }
3353
3354            updateChart(chartData);
3355        };
3356
3357        /**
3358         * Update the mortgage donut chart
3359         * @param {Array} chartData - Chart data for visualization
3360         */
3361        const updateChart = function (chartData) {
3362            const ctx = $('#mortgage-calculator-chart')[0].getContext('2d');
3363
3364            if (window.myChart) {
3365                window.myChart.destroy();
3366            }
3367
3368            window.myChart = new Chart(ctx, {
3369                type: 'doughnut',
3370                data: {
3371                    datasets: [
3372                        {
3373                            data: chartData.map((item) => item.value),
3374                            backgroundColor: chartData.map(
3375                                (item) => item.color
3376                            ),
3377                        },
3378                    ],
3379                },
3380                options: {
3381                    cutoutPercentage: 85,
3382                    responsive: false,
3383                    tooltips: false,
3384                },
3385            });
3386        };
3387
3388        /**
3389         * Initialize the mortgage calculator
3390         */
3391        const initMortgageCalculator = function () {
3392            // If the calculator form exists, set up event handlers
3393            if ($('#houzez-calculator-form').length > 0) {
3394                $('#houzez-calculator-form input').on(
3395                    'input',
3396                    mortgageCalculation
3397                );
3398
3399                // Calculate on page load
3400                mortgageCalculation();
3401            }
3402        };
3403
3404        /**
3405         * Initialize the mortgage module
3406         */
3407        const init = function () {
3408            initMortgageCalculator();
3409        };
3410
3411        // Public API
3412        return {
3413            init: init,
3414            mortgageCalculation: mortgageCalculation,
3415            updateChart: updateChart,
3416        };
3417    })();
3418
3419    /**
3420     * Property Contact Forms Module
3421     * Handles property contact forms functionality
3422     */
3423    houzez.ContactForms = (function () {
3424        /**
3425         * Handle agent contact form submittal
3426         */
3427        const propertyAgentContactForm = function () {
3428            $('.houzez_agent_property_form').on('click', function (e) {
3429                e.preventDefault();
3430
3431                var $this = $(this);
3432                var $form = $this.parents('form');
3433                var $form_wrap = $this.parents('.property-form-wrap');
3434                var $result = $form_wrap.find('.form_messages');
3435                var $is_bottom = $('.is_bottom').val();
3436
3437                if ($is_bottom == 'bottom') {
3438                    $result = $form.find('.form_messages');
3439                }
3440                $result.empty();
3441
3442                $.ajax({
3443                    type: 'post',
3444                    url: houzez.Core.config.ajaxurl,
3445                    data: $form.serialize(),
3446                    dataType: 'JSON',
3447                    beforeSend: function () {
3448                        $this.attr('disabled', true);
3449                        $this.find('.houzez-loader-js').addClass('loader-show');
3450                    },
3451                    success: function (response) {
3452                        if (response.success) {
3453                            // Clear form fields
3454                            $form
3455                                .find(
3456                                    'input[name="name"], input[name="mobile"], input[name="email"]'
3457                                )
3458                                .val('');
3459                            $form.find('textarea').val('');
3460
3461                            // Show success message
3462                            houzez.Core.util.showSuccess($result, response.msg);
3463                        } else {
3464                            // Show error message
3465                            houzez.Core.util.showError($result, response.msg);
3466                        }
3467
3468                        // Reset captcha (supports both reCaptcha and Turnstile)
3469                        houzez.Core.util.resetCaptcha($form);
3470
3471                        // Handle redirection if needed
3472                        if (
3473                            houzez_vars.agent_redirection != '' &&
3474                            response.success
3475                        ) {
3476                            setTimeout(function () {
3477                                window.location.replace(
3478                                    houzez_vars.agent_redirection
3479                                );
3480                            }, 500);
3481                        }
3482                    },
3483                    error: function (xhr, status, error) {
3484                        var err = eval('(' + xhr.responseText + ')');
3485                        console.log(err.Message);
3486                    },
3487                    complete: function () {
3488                        $this.attr('disabled', false);
3489                        $this
3490                            .find('.houzez-loader-js')
3491                            .removeClass('loader-show');
3492                    },
3493                });
3494            });
3495        };
3496
3497        /**
3498         * Handle property schedule tour form submission
3499         */
3500        const scheduleTour = function () {
3501            $('.schedule_contact_form').on('click', function (e) {
3502                e.preventDefault();
3503
3504                const $this = $(this);
3505                const $form = $this.parents('form');
3506                const $messages = $form.find('.form_messages');
3507
3508                $.ajax({
3509                    method: $form.attr('method'),
3510                    dataType: 'JSON',
3511                    url: houzez.Core.config.ajaxurl,
3512                    data: $form.serialize(),
3513                    beforeSend: function () {
3514                        $this.attr('disabled', true);
3515                        $this.find('.houzez-loader-js').addClass('loader-show');
3516                    },
3517                    success: function (response) {
3518                        if (response.success) {
3519                            houzez.Core.util.showSuccess(
3520                                $messages,
3521                                response.msg
3522                            );
3523                        } else {
3524                            houzez.Core.util.showError($messages, response.msg);
3525                        }
3526                    },
3527                    error: function (xhr, status, error) {
3528                        var err = eval('(' + xhr.responseText + ')');
3529                        console.log(err.Message);
3530                    },
3531                    complete: function () {
3532                        $this.attr('disabled', false);
3533                        $this
3534                            .find('.houzez-loader-js')
3535                            .removeClass('loader-show');
3536                    },
3537                });
3538            });
3539
3540            /* ------------------------------------------------------------------------ */
3541            /*  Schedule tour v2
3542            /* ------------------------------------------------------------------------ */
3543
3544            // $('.tour-day-form-slide-arrow.next').click(function (e) {
3545            //     $('.property-schedule-tour-day-form-slide').addClass('end');
3546            //     $('.property-schedule-tour-day-form-slide').removeClass(
3547            //         'start'
3548            //     );
3549            // });
3550            // $('.tour-day-form-slide-arrow.prev').click(function (e) {
3551            //     $('.property-schedule-tour-day-form-slide').addClass('start');
3552            //     $('.property-schedule-tour-day-form-slide').removeClass('end');
3553            // });
3554        };
3555
3556        /**
3557         * Handle contact agent form submission
3558         */
3559        const contactRealtor = function () {
3560            $(document).on('click', '.contact-realtor-btn', function (e) {
3561                e.preventDefault();
3562                var current_element = $(this);
3563                var $this = $(this);
3564                var $form = $this.parents('form');
3565                var $result = $form.find('.form_messages');
3566
3567                jQuery.ajax({
3568                    type: 'post',
3569                    url: houzez.Core.config.ajaxurl,
3570                    data: $form.serialize(),
3571                    method: $form.attr('method'),
3572                    dataType: 'JSON',
3573
3574                    beforeSend: function () {
3575                        $this.find('.houzez-loader-js').addClass('loader-show');
3576                    },
3577                    success: function (res) {
3578                        if (res.success) {
3579                            houzez.Core.util.showSuccess($result, res.msg);
3580                        } else {
3581                            // Show error message
3582                            houzez.Core.util.showError($result, res.msg);
3583                        }
3584
3585                        $this
3586                            .find('.houzez-loader-js')
3587                            .removeClass('loader-show');
3588                        // Reset captcha (supports both reCaptcha and Turnstile)
3589                        houzez.Core.util.resetCaptcha($form);
3590                    },
3591                    error: function (xhr, status, error) {
3592                        var err = eval('(' + xhr.responseText + ')');
3593                        houzez.Core.util.showError($result, err.Message);
3594                    },
3595                    complete: function () {
3596                        $this
3597                            .find('.houzez-loader-js')
3598                            .removeClass('loader-show');
3599                    },
3600                });
3601            });
3602        };
3603
3604        /**
3605         * Initialize the module
3606         */
3607        const init = function () {
3608            propertyAgentContactForm();
3609            contactRealtor();
3610            if (houzez.Core.config.is_singular_property) {
3611                scheduleTour();
3612            }
3613        };
3614
3615        // Public API
3616        return {
3617            init: init,
3618            propertyAgentContactForm: propertyAgentContactForm,
3619            scheduleTour: scheduleTour,
3620            contactRealtor: contactRealtor,
3621        };
3622    })();
3623
3624    /**
3625     * Single Property Module
3626     * Handles single property functionality
3627     */
3628    houzez.SingleProperty = (function () {
3629        /**
3630         * Refresh the media tabs
3631         */
3632        const refreshMediaTabs = () => {
3633            // Track if tour has been loaded
3634            let tourLoaded = false;
3635
3636            // Use class selector instead of ID
3637            let tourTabs = document.querySelectorAll(
3638                '.houzez-360-virtual-media-tab'
3639            );
3640
3641            // Check if elements exist before adding event listeners
3642            if (tourTabs.length > 0) {
3643                tourTabs.forEach((tourTab) => {
3644                    tourTab.addEventListener('shown.bs.tab', () => {
3645                        // Only load if not already loaded
3646                        if (!tourLoaded) {
3647                            // Show loading indicator
3648                            $('.loader-360').show();
3649
3650                            // Properly reload the content
3651                            $('#pills-360tour').load(
3652                                window.location.href +
3653                                    ' #virtual-tour-iframe-container',
3654                                function () {
3655                                    // Hide loading indicator when done
3656                                    $('.loader-360').hide();
3657                                    // Mark as loaded
3658                                    tourLoaded = true;
3659                                }
3660                            );
3661                        }
3662                    });
3663                });
3664            }
3665
3666            // Handle gallery tab switch - refresh slider and load images
3667            var galleryTab = document.getElementById('pills-gallery-tab');
3668            if (galleryTab) {
3669                galleryTab.addEventListener('shown.bs.tab', function () {
3670                    // Handle LightSlider for standard gallery
3671                    var slider = $('#property-gallery-js');
3672                    if (slider.length && slider.data('lightSlider')) {
3673                        // Force refresh the slider
3674                        slider.data('lightSlider').refresh();
3675
3676                        // Trigger lazy image loading for visible slides
3677                        slider
3678                            .find('.houzez-gallery-img[data-lazy]')
3679                            .each(function () {
3680                                var $img = $(this);
3681                                var lazySrc = $img.data('lazy');
3682                                if (
3683                                    lazySrc &&
3684                                    (!$img.attr('src') ||
3685                                        !$img.attr('src').includes(lazySrc))
3686                                ) {
3687                                    $img.attr('src', lazySrc);
3688                                }
3689                            });
3690                    }
3691
3692                    // Also handle Slick slider for v5 layouts
3693                    var slickSlider = $('.listing-slider-variable-width');
3694                    if (
3695                        slickSlider.length &&
3696                        slickSlider.hasClass('slick-initialized')
3697                    ) {
3698                        slickSlider.slick('setPosition');
3699                        slickSlider.slick('refresh');
3700                    }
3701                });
3702            }
3703        };
3704
3705        /**
3706         * Initialize the sticky property navigation
3707         */
3708        const propertyDetailNav = function () {
3709            let houzez_listing_nav = $('.property-navigation-wrap');
3710            if (
3711                houzez.Core.config.property_detail_nav === 'yes' &&
3712                houzez_listing_nav.length > 0
3713            ) {
3714                let header_area = $('.header-main-wrap');
3715
3716                if (!header_area.length) {
3717                    header_area = $('#header-hz-elementor');
3718                }
3719
3720                const header_area_height = header_area.innerHeight();
3721                const get_header_search = $('.advanced-search-header');
3722                const get_header_search_height =
3723                    get_header_search.innerHeight();
3724                let scroll_nav_height;
3725
3726                if (get_header_search.length) {
3727                    scroll_nav_height =
3728                        header_area_height + get_header_search_height;
3729                } else {
3730                    scroll_nav_height = header_area_height;
3731                }
3732
3733                if ($(window).width() >= 992) {
3734                    const contentTop = $('section.property-wrap').offset().top;
3735
3736                    $(window).scroll(function () {
3737                        const scroll = $(window).scrollTop();
3738                        const admin_nav = houzez.Core.config.wpadminbar_height;
3739
3740                        let top_position = 0;
3741                        if (admin_nav > 0) {
3742                            top_position = admin_nav;
3743                        }
3744
3745                        if (scroll > contentTop) {
3746                            $('.property-navigation-wrap').css({
3747                                top: top_position,
3748                            });
3749                            $('.property-navigation-wrap').fadeIn();
3750                            $('.property-navigation-wrap').addClass(
3751                                'nav-fixed'
3752                            );
3753                        } else if (scroll == 0) {
3754                            $('.property-navigation-wrap').removeAttr('style');
3755                            $('.property-navigation-wrap').removeClass(
3756                                'nav-fixed'
3757                            );
3758                            $('.property-navigation-wrap').fadeOut();
3759                        }
3760                    });
3761
3762                    $(window).on('scroll', function () {
3763                        $('.property-section-wrap').each(function () {
3764                            if (
3765                                $(window).scrollTop() >=
3766                                $(this).offset().top - 86
3767                            ) {
3768                                var id = $(this).attr('id');
3769                                $('.target').removeClass('active');
3770                                $('.target[href="#' + id + '"]').addClass(
3771                                    'active'
3772                                );
3773                            } else if ($(window).scrollTop() <= 0) {
3774                                $('.target').removeClass('active');
3775                            }
3776                        });
3777                    });
3778
3779                    $('.property-navigation-item a.target').on(
3780                        'click',
3781                        function (e) {
3782                            e.preventDefault(); // Prevent the default jump-to-anchor behavior
3783
3784                            // Grab the href (e.g. "#section1") and find its offset from the top
3785                            let targetId = $(this).attr('href');
3786                            let $target = $(targetId);
3787                            if ($target.length) {
3788                                let offsetTop =
3789                                    $target.offset().top - header_area_height;
3790
3791                                // Animate the scroll to that position (600ms duration)
3792                                $('html, body').animate(
3793                                    { scrollTop: offsetTop },
3794                                    100
3795                                );
3796                            }
3797                        }
3798                    );
3799                }
3800            }
3801        };
3802
3803        const propertyFancyboxGallery = function () {
3804            Fancybox.bind(
3805                'a[data-fancybox][data-houzez-fancybox], .listing-slider-variable-width .slick-slide:not(.slick-cloned) a[data-fancybox="gallery-variable-width"]',
3806                {
3807                    Hash: false,
3808                    Thumbs: {
3809                        type: 'modern', // or 'classic'
3810                    },
3811                }
3812            );
3813        };
3814
3815        const propertyDetailLightbox = function () {
3816            $('.btn-expand').on('click', function () {
3817                $('.lightbox-gallery-wrap').toggleClass(
3818                    'lightbox-gallery-full-wrap'
3819                );
3820                $('.lightbox-slider').slick('setPosition');
3821            });
3822
3823            $('.btn-email').on('click', function () {
3824                $('.lightbox-form-wrap').toggleClass('lightbox-form-wrap-show');
3825            });
3826        };
3827
3828        /**
3829         * Handle print property feature
3830         */
3831        const printProperty = function () {
3832            $('.houzez-print').on('click', function (e) {
3833                e.preventDefault();
3834                const propID = $(this).attr('data-propid');
3835                const printWindow = window.open(
3836                    '',
3837                    'Print Me',
3838                    'width=800, height=842'
3839                );
3840
3841                $.ajax({
3842                    type: 'post',
3843                    url: houzez.Core.config.ajaxurl,
3844                    data: {
3845                        action: 'houzez_create_print',
3846                        propid: propID,
3847                    },
3848                    success: function (data) {
3849                        printWindow.document.write(data);
3850                        printWindow.document.close();
3851                        printWindow.focus();
3852                    },
3853                    error: function (xhr, status, error) {
3854                        var err = eval('(' + xhr.responseText + ')');
3855                        console.log(err.Message);
3856                    },
3857                });
3858                return false;
3859            });
3860        };
3861
3862        const adjustTabContentHeight = function (forceAdjust = false) {
3863            // Get the tab content element
3864            const $tabContent = $('.hs-property-gallery-wrap .tab-content');
3865            if (!$tabContent.length) return;
3866
3867            $('.mobile-property-tools a.nav-link').on('click', function () {
3868                const tabHeight = $('.tab-content').outerHeight(true);
3869                // Now set the fixed height
3870                $tabContent.css({
3871                    height: tabHeight + 'px',
3872                    transition: 'height 0.3s ease',
3873                });
3874            });
3875        };
3876
3877        /**
3878         * Initialize the module
3879         */
3880        const init = function () {
3881            if (houzez.Core.config.is_singular_property) {
3882                propertyDetailNav();
3883                propertyFancyboxGallery();
3884                propertyDetailLightbox();
3885                printProperty();
3886                refreshMediaTabs();
3887
3888                // Initialize Bootstrap tooltips for media tab buttons
3889                var tooltipTriggerList = [].slice.call(
3890                    document.querySelectorAll(
3891                        '#pills-tab [data-bs-tooltip="tooltip"]'
3892                    )
3893                );
3894                tooltipTriggerList.forEach(function (tooltipTriggerEl) {
3895                    new bootstrap.Tooltip(tooltipTriggerEl, {
3896                        placement: 'bottom',
3897                        trigger: 'hover',
3898                    });
3899                });
3900
3901                if (houzez.Core.is_mobile) {
3902                    adjustTabContentHeight();
3903                }
3904            }
3905        };
3906
3907        // Public API
3908        return {
3909            init: init,
3910            propertyDetailNav: propertyDetailNav,
3911            propertyFancyboxGallery: propertyFancyboxGallery,
3912            propertyDetailLightbox: propertyDetailLightbox,
3913            printProperty: printProperty,
3914            refreshMediaTabs: refreshMediaTabs,
3915            adjustTabContentHeight: adjustTabContentHeight,
3916        };
3917    })();
3918
3919    /**
3920     * Realtor Stats Module
3921     * Handles realtor stats functionality
3922     */
3923    houzez.RealtorStats = (function () {
3924        /* ------------------------------------------------------------------------ */
3925        /*  Types chart for agent and agency
3926     /* ------------------------------------------------------------------------ */
3927        const init = function () {
3928            $('.houzez-realtor-stats-js[id^="stats-property-"]').each(
3929                function () {
3930                    var $div = $(this);
3931                    var token = $div.data('token');
3932
3933                    var statsID = 'stats-property-' + token;
3934                    if ($('#' + statsID).length > 0) {
3935                        var chartData = $('#' + statsID).data('chart');
3936                        var ctx = document
3937                            .getElementById(statsID)
3938                            .getContext('2d');
3939                        var myDoughnutChart = new Chart(ctx, {
3940                            type: 'doughnut',
3941                            data: {
3942                                datasets: [
3943                                    {
3944                                        data: chartData,
3945                                        backgroundColor: [
3946                                            'rgba(255, 99, 132, 0.5)',
3947                                            'rgba(54, 162, 235, 0.5)',
3948                                            'rgba(255, 206, 86, 0.5)',
3949                                            'rgba(75, 192, 192, 0.5)',
3950                                        ],
3951                                        borderColor: [
3952                                            'rgba(255, 99, 132, 1)',
3953                                            'rgba(54, 162, 235, 1)',
3954                                            'rgba(255, 206, 86, 1)',
3955                                            'rgba(75, 192, 192, 1)',
3956                                        ],
3957                                        borderWidth: 1,
3958                                    },
3959                                ],
3960                            },
3961                            options: {
3962                                cutoutPercentage: 60,
3963                                responsive: false,
3964                                tooltips: false,
3965                            },
3966                        });
3967                    }
3968                }
3969            );
3970        };
3971
3972        return {
3973            init: init,
3974        };
3975    })();
3976
3977    /**
3978     * Logo Module
3979     * Handles logo functionality
3980     */
3981    houzez.Logo = (function () {
3982        /**
3983         * Handle retina logo display
3984         */
3985        const retinaLogo = function () {
3986            // Get retina logo paths from global vars
3987            const retina_logo = houzez_vars.retina_logo || '';
3988            const retina_logo_splash = houzez_vars.retina_logo_splash || '';
3989            const retina_logo_mobile = houzez_vars.retina_logo_mobile || '';
3990            const retina_logo_mobile_splash =
3991                houzez_vars.retina_logo_mobile_splash || '';
3992            const transparent_logo = houzez_vars.transparent_logo || false;
3993
3994            if (
3995                retina_logo !== '' ||
3996                retina_logo_splash != '' ||
3997                retina_logo_mobile != '' ||
3998                retina_logo_mobile_splash != ''
3999            ) {
4000                if (window.devicePixelRatio == 2) {
4001                    if (retina_logo != '') {
4002                        $('.logo-desktop img').attr('src', retina_logo);
4003                    }
4004
4005                    if (retina_logo_splash != '') {
4006                        $('.logo-splash img').attr('src', retina_logo_splash);
4007                    }
4008
4009                    if (retina_logo_mobile != '') {
4010                        $('.logo-mobile img').attr('src', retina_logo_mobile);
4011                    }
4012                }
4013            }
4014
4015            // Special handling for transparent logo in retina screens
4016            if (window.devicePixelRatio == 2 && transparent_logo) {
4017                if (retina_logo_splash != '') {
4018                    houzez_vars.custom_logo_splash = retina_logo_splash;
4019                }
4020                if (retina_logo != '') {
4021                    houzez_vars.simple_logo = retina_logo;
4022                }
4023            }
4024        };
4025
4026        const init = function () {
4027            retinaLogo();
4028        };
4029
4030        return {
4031            init: init,
4032            retinaLogo: retinaLogo,
4033        };
4034    })();
4035
4036    /**
4037     * Sticky Module
4038     * Handles sticky functionality
4039     */
4040    houzez.Sticky = (function () {
4041        // Cache DOM elements
4042        const $win = $(window);
4043        const $header_area = $('.header-main-wrap');
4044        const $header_nav = $('#header-section');
4045        const $header_mobile = $('#header-mobile');
4046        const $desktop_search_nav = $('.desktop-search-nav');
4047        const $mobile_search_nav = $('.mobile-search-nav');
4048        const $top_banner_wrap = $('.top-banner-wrap');
4049
4050        // Configuration variables
4051        let houzezStickyTop = 0;
4052        let adminBarHeight = houzez.Core.config.wpadminbar_height;
4053        let header_area_height = $header_area.innerHeight() || 0;
4054        let desktop_search_nav_height = $desktop_search_nav.innerHeight() || 0;
4055        let mobile_search_nav_height = $mobile_search_nav.innerHeight() || 0;
4056        let houzez_nav_sticky_height = $header_nav.innerHeight() || 0;
4057        let top_banner_wrap_height = 0;
4058
4059        let only_nav_sticky = $header_nav.data('sticky') || 0;
4060        let mobile_menu_sticky = $header_mobile.data('sticky') || 0;
4061
4062        if (
4063            houzez.Core.config.property_detail_nav == 'yes' &&
4064            houzez.Core.config.is_singular_property
4065        ) {
4066            only_nav_sticky = 0;
4067        }
4068
4069        /**
4070         * Calculate sticky heights and positions
4071         */
4072        const calculateStickyHeights = function () {
4073            houzezStickyTop = houzez_nav_sticky_height;
4074
4075            if (adminBarHeight > 0) {
4076                houzezStickyTop += adminBarHeight;
4077            }
4078
4079            // Update top banner height
4080            top_banner_wrap_height = $top_banner_wrap.innerHeight() || 0;
4081
4082            // Search position calculations
4083            let make_search_sticky_position =
4084                header_area_height + desktop_search_nav_height;
4085            let searchStickyPlus = 100;
4086            let search_under_navigation = true;
4087
4088            if (houzez.Core.is_mobile()) {
4089                make_search_sticky_position += 40;
4090            }
4091
4092            if (houzez_vars.search_position === 'under_banner') {
4093                make_search_sticky_position =
4094                    header_area_height + top_banner_wrap_height;
4095                searchStickyPlus = 0;
4096
4097                if (houzez_vars.is_transparent) {
4098                    searchStickyPlus = 60;
4099                }
4100
4101                search_under_navigation = false;
4102            }
4103
4104            return {
4105                make_search_sticky_position,
4106                searchStickyPlus,
4107                search_under_navigation,
4108            };
4109        };
4110
4111        /**
4112         * Handle sticky navigation
4113         */
4114        const stickyNav = function () {
4115            $win.scroll(function () {
4116                const scroll = $win.scrollTop();
4117                let top_banner_margin = 0;
4118                const thisHeight = $header_nav.outerHeight();
4119                top_banner_margin = thisHeight;
4120                if (houzez_vars.is_top_header != 0) {
4121                    const top_bar_wrap = $('.top-bar-wrap').height();
4122                    top_banner_margin = top_banner_margin + top_bar_wrap;
4123                }
4124
4125                // Check if sticky is disabled
4126                if (only_nav_sticky === 0) {
4127                    return;
4128                }
4129                // Update logo if transparent
4130                if (houzez_vars.transparent_logo) {
4131                    $('.logo-splash img').attr('src', houzez_vars.simple_logo);
4132                }
4133
4134                if (scroll > header_area_height) {
4135                    $header_nav.addClass('sticky-nav-area');
4136                    $header_nav.css('top', adminBarHeight);
4137
4138                    if (houzez_vars.transparent_logo) {
4139                        $header_area.removeClass('header-transparent-wrap');
4140                        $('.top-banner-wrap').css(
4141                            'margin-top',
4142                            '-' + top_banner_margin + 'px'
4143                        );
4144                        $('body.houzez-header-elementor .content-wrap').css(
4145                            'margin-top',
4146                            '-' + top_banner_margin + 'px'
4147                        );
4148                        $('#main-wrap > div.elementor').css(
4149                            'margin-top',
4150                            '-' + top_banner_margin + 'px'
4151                        );
4152                    }
4153
4154                    if (scroll >= header_area_height + 20) {
4155                        $header_nav.addClass('houzez-in-view');
4156                        $('#main-wrap').css('margin-top', thisHeight);
4157                    }
4158                } else {
4159                    $header_nav.removeClass('sticky-nav-area');
4160                    $header_nav.removeAttr('style');
4161
4162                    if (houzez_vars.transparent_logo) {
4163                        $header_area.addClass('header-transparent-wrap');
4164                        $('.top-banner-wrap').css('margin-top', 0);
4165                        $('body.houzez-header-elementor .content-wrap').css(
4166                            'margin-top',
4167                            0
4168                        );
4169                        $('#main-wrap > div.elementor').css('margin-top', 0);
4170                        $('.logo-splash img').attr(
4171                            'src',
4172                            houzez_vars.custom_logo_splash
4173                        );
4174                    }
4175
4176                    if (scroll <= header_area_height + 20) {
4177                        $header_nav.removeClass('houzez-in-view');
4178                    }
4179
4180                    $('#main-wrap').css('margin-top', 0);
4181                }
4182            });
4183        };
4184
4185        const stickyMobileNav = function () {
4186            let thisHeight = $header_mobile.outerHeight();
4187            $win.scroll(function () {
4188                let scroll = $win.scrollTop();
4189
4190                if (mobile_menu_sticky === 0 || !houzez.Core.is_mobile()) {
4191                    return;
4192                }
4193
4194                if (scroll > header_area_height + 50) {
4195                    $header_mobile.addClass('sticky-nav-area');
4196                    //$header_mobile.css('top', adminBarHeight);
4197
4198                    if (scroll >= header_area_height + 60) {
4199                        $header_mobile.addClass('houzez-in-view');
4200                        $('#main-wrap').css('margin-top', thisHeight);
4201                    }
4202                } else {
4203                    $header_mobile.removeClass('sticky-nav-area');
4204                    $header_mobile.removeAttr('style');
4205
4206                    if (scroll <= header_area_height + 60) {
4207                        $header_mobile.removeClass('houzez-in-view');
4208                    }
4209
4210                    $('#main-wrap').css('margin-top', 0);
4211                }
4212            });
4213        };
4214
4215        /**
4216         * Handle sticky search
4217         */
4218        const stickySearch = function () {
4219            const { make_search_sticky_position, searchStickyPlus } =
4220                calculateStickyHeights();
4221
4222            $win.scroll(function () {
4223                const scroll = $win.scrollTop();
4224                const thisHeight = $desktop_search_nav.outerHeight();
4225                const hidden_data = $desktop_search_nav.data('hidden');
4226
4227                if (scroll >= make_search_sticky_position) {
4228                    $desktop_search_nav.addClass('sticky-search-area');
4229
4230                    if (hidden_data) {
4231                        $desktop_search_nav.removeClass('search-hidden');
4232                    }
4233
4234                    if (!houzez.Core.is_mobile()) {
4235                        $desktop_search_nav.css('top', adminBarHeight);
4236                    }
4237
4238                    if (
4239                        scroll >=
4240                        make_search_sticky_position + searchStickyPlus
4241                    ) {
4242                        $desktop_search_nav.addClass('houzez-in-view');
4243
4244                        if (!hidden_data) {
4245                            $('#main-wrap').css('padding-top', thisHeight);
4246                        }
4247                    }
4248                } else {
4249                    $desktop_search_nav.removeClass('sticky-search-area');
4250                    $desktop_search_nav.removeAttr('style');
4251
4252                    if (scroll <= make_search_sticky_position + 20) {
4253                        $desktop_search_nav.removeClass('houzez-in-view');
4254                    }
4255
4256                    if (hidden_data) {
4257                        $desktop_search_nav.addClass('search-hidden');
4258                    }
4259
4260                    if (!hidden_data) {
4261                        $('#main-wrap').css('padding-top', 0);
4262                    }
4263                }
4264            });
4265        };
4266
4267        const stickyMobileSearch = function () {
4268            let thisHeight = $mobile_search_nav.outerHeight();
4269            let { make_search_sticky_position, searchStickyPlus } =
4270                calculateStickyHeights();
4271
4272            let make_mobile_search_sticky_position = thisHeight;
4273            let searchInView = thisHeight;
4274            if (adminBarHeight > 0) {
4275                make_mobile_search_sticky_position += adminBarHeight;
4276                searchInView += adminBarHeight + 46;
4277            }
4278
4279            $win.scroll(function () {
4280                const scroll = $win.scrollTop();
4281
4282                if (scroll >= make_mobile_search_sticky_position) {
4283                    $mobile_search_nav.addClass('sticky-search-area');
4284                    $mobile_search_nav.css('top', adminBarHeight);
4285
4286                    if (scroll >= searchInView) {
4287                        $mobile_search_nav.addClass('houzez-in-view');
4288
4289                        $('#main-wrap').css('padding-top', thisHeight);
4290                    }
4291                } else {
4292                    $mobile_search_nav.removeClass('sticky-search-area');
4293                    $mobile_search_nav.removeAttr('style');
4294
4295                    if (scroll <= searchInView + 20) {
4296                        $mobile_search_nav.removeClass('houzez-in-view');
4297                    }
4298
4299                    $('#main-wrap').css('padding-top', 0);
4300                }
4301            });
4302        };
4303
4304        /**
4305         * Initialize all functionality
4306         */
4307        const init = function () {
4308            // Calculate heights and initialize variables
4309            calculateStickyHeights();
4310
4311            // Initialize functionality based on configuration
4312            const only_nav_sticky = $header_nav.data('sticky');
4313            const desktop_header_search_enabled =
4314                $desktop_search_nav.data('sticky');
4315            const mobile_header_search_enabled =
4316                $mobile_search_nav.data('sticky');
4317
4318            // Initialize sticky navigation if enabled
4319            if (only_nav_sticky && !houzez.Core.is_mobile()) {
4320                // apply new mobile
4321                stickyNav();
4322
4323                // Special handling for compare table if needed
4324                if ($('.compare-table').length > 0) {
4325                    var compare_top_margin = $desktop_search_nav.outerHeight();
4326                    $('.compare-table thead th').css(
4327                        'top',
4328                        compare_top_margin + 25
4329                    );
4330                }
4331            }
4332
4333            stickyMobileNav();
4334
4335            if (mobile_header_search_enabled) {
4336                stickyMobileSearch();
4337            }
4338
4339            // Initialize sticky search if enabled
4340            if (desktop_header_search_enabled && !houzez.Core.is_mobile()) {
4341                stickySearch();
4342            }
4343        };
4344
4345        // Public API
4346        return {
4347            init: init,
4348            stickyNav: stickyNav,
4349            stickySearch: stickySearch,
4350        };
4351    })();
4352
4353    /**
4354     * Search Module
4355     * Handles search functionality
4356     */
4357    houzez.Search = (function () {
4358        /**
4359         * Initialize mobile search overlay
4360         */
4361        const mobileSearchOverlay = function () {
4362            // Calculate admin bar height and set overlay height
4363            const setOverlayHeight = function () {
4364                const $adminBar = houzez.Core.config.wpadminbar_height;
4365                const $searchOverlay = $('#overlay-search-advanced-module');
4366                const windowHeight = $(window).height();
4367
4368                if ($adminBar > 0 && $searchOverlay.length) {
4369                    const adminBarHeight = $adminBar;
4370                    $searchOverlay.css({
4371                        height: 'calc(100vh)',
4372                        zIndex: '999',
4373                        top: adminBarHeight,
4374                    });
4375                } else if ($searchOverlay.length) {
4376                    $searchOverlay.css({
4377                        height: '100vh',
4378                        top: '0',
4379                    });
4380                }
4381            };
4382
4383            // Set initial height
4384            setOverlayHeight();
4385
4386            // Update height on window resize
4387            $(window).on('resize', function () {
4388                setOverlayHeight();
4389            });
4390
4391            // Toggle overlay
4392            $(
4393                '.mobile-search-nav, .overlay-search-module-close, .overly_is_halfmap .half-map-search-js-btn'
4394            ).click(function () {
4395                $('#overlay-search-advanced-module').toggleClass('open');
4396            });
4397        };
4398
4399        /**
4400         * Initialize search status tabs
4401         */
4402        const initSearchTabs = function () {
4403            $('.houzez-status-tabs li a').on('click', function (e) {
4404                e.preventDefault();
4405                let $this = $(this);
4406                let status = $this.data('val');
4407
4408                $('#search-tabs').val(status);
4409
4410                const $form = $('.houzez-search-form-js');
4411                statusChangeHandler(status, $form);
4412            });
4413        };
4414
4415        /**
4416         * Changes price dropdown options based on property status
4417         * @param {string} propertyStatus - Selected property status
4418         * @param {object} $form - jQuery form object
4419         * @returns {boolean} - Returns true if successful, false otherwise
4420         */
4421        const statusChangeHandler = function (propertyStatus, $form) {
4422            // Validate parameters
4423            if (!propertyStatus || !$form || !$form.length) {
4424                return false;
4425            }
4426
4427            const isForRent = propertyStatus == houzez_vars.for_rent;
4428            const rentSelector = '.prices-only-for-rent';
4429            const allSelector = '.prices-for-all';
4430
4431            // Toggle rent-specific price options
4432            if (isForRent) {
4433                $form.find(allSelector).addClass('hide');
4434                $form
4435                    .find(`${allSelector} select`)
4436                    .attr('disabled', 'disabled');
4437                $form.find(rentSelector).removeClass('hide');
4438                $form.find(`${rentSelector} select`).removeAttr('disabled');
4439                $form.find(`${rentSelector} select`).selectpicker('refresh');
4440            } else {
4441                $form.find(rentSelector).addClass('hide');
4442                $form
4443                    .find(`${rentSelector} select`)
4444                    .attr('disabled', 'disabled');
4445                $form.find(allSelector).removeClass('hide');
4446                $form.find(`${allSelector} select`).removeAttr('disabled');
4447                $form.find(`${allSelector} select`).selectpicker('refresh');
4448            }
4449
4450            return true;
4451        };
4452
4453        /**
4454         * Initialize property status change event handlers
4455         * Sets up event listeners for status changes and initializes the UI
4456         */
4457        const initStatusChangeHandlers = function () {
4458            // Status dropdown change event
4459            $('select.status-js').on('change', function (e) {
4460                // Prevent event from bubbling up to the wrapper div
4461                e.stopPropagation();
4462
4463                const selected_status = $(this).val();
4464                const $form = $(this).parents('form');
4465                statusChangeHandler(selected_status, $form);
4466            });
4467
4468            // Status tab click event
4469            $('.status-tab-js').on('click', function () {
4470                const tab_selected_status = $(this).data('val');
4471                const $form = $(this).parents('form');
4472                statusChangeHandler(tab_selected_status, $form);
4473            });
4474
4475            // Initialize on page load based on current status
4476            const $searchForm = $('.houzez-search-form-js');
4477            const $builderForm = $('.houzez-search-builder-form-js');
4478
4479            /* On page load*/
4480            const selected_status = $('select.status-js').val();
4481            if (selected_status == houzez_vars.for_rent) {
4482                statusChangeHandler(selected_status, $searchForm);
4483            } else {
4484                statusChangeHandler('dummy', $searchForm);
4485            }
4486
4487            /* On page load status tab */
4488            const selected_status_tab = $('.status-tab-js').val();
4489            if (selected_status_tab == houzez_vars.for_rent) {
4490                statusChangeHandler(selected_status_tab, $builderForm);
4491            } else {
4492                statusChangeHandler('dummy', $builderForm);
4493            }
4494        };
4495
4496        /**
4497         * Fill the distance range slider with gradient background
4498         */
4499        const fillDistanceRangeSlider = function (slider) {
4500            if (!slider) return;
4501
4502            const value =
4503                ((slider.value - slider.min) / (slider.max - slider.min)) * 100;
4504            const isRTL = document.documentElement.dir === 'rtl';
4505            const direction = isRTL ? 'to left' : 'to right';
4506            slider.style.background = `linear-gradient(
4507                ${direction},
4508                ${houzez_vars.primary_color} 0%,
4509                ${houzez_vars.primary_color} ${value}%,
4510                #dce0e0 ${value}%,
4511                #dce0e0 100%
4512            )`;
4513        };
4514
4515        /**
4516         * Initialize the distance range slider functionality
4517         */
4518        const initDistanceRangeSlider = function () {
4519            const rangeInput = document.getElementById('radius-range-text');
4520            const rangeSlider = document.getElementById('radius-range-slider');
4521            const radiusValue = document.getElementById('radius-range-value');
4522            const radiusCheckbox = document.getElementById('use_radius');
4523            const distanceRange = document.querySelector('.distance-range');
4524
4525            if (!rangeInput || !rangeSlider || !radiusValue) return;
4526
4527            // Sync input and slider
4528            rangeInput.addEventListener('input', (e) => {
4529                const value = e.target.value;
4530                rangeSlider.value = value;
4531                radiusValue.value = value;
4532                if (distanceRange) {
4533                    distanceRange.value = value;
4534                }
4535                fillDistanceRangeSlider(rangeSlider);
4536            });
4537
4538            rangeSlider.addEventListener('input', (e) => {
4539                const value = e.target.value;
4540                rangeInput.value = value;
4541                radiusValue.value = value;
4542                if (distanceRange) {
4543                    distanceRange.value = value;
4544                }
4545                fillDistanceRangeSlider(rangeSlider);
4546            });
4547
4548            // Set initial values
4549            const defaultRadius = rangeSlider.value;
4550            rangeInput.value = defaultRadius;
4551            radiusValue.value = defaultRadius;
4552            if (distanceRange) {
4553                distanceRange.value = defaultRadius;
4554            }
4555
4556            // Initial fill
4557            fillDistanceRangeSlider(rangeSlider);
4558
4559            // Enable/disable based on checkbox
4560            if (radiusCheckbox) {
4561                radiusCheckbox.addEventListener('change', (e) => {
4562                    const isEnabled = e.target.checked;
4563                    rangeInput.disabled = !isEnabled;
4564                    rangeSlider.disabled = !isEnabled;
4565                    if (isEnabled) {
4566                        radiusValue.value = rangeSlider.value;
4567                        if (distanceRange) {
4568                            distanceRange.value = rangeSlider.value;
4569                        }
4570                    } else {
4571                        radiusValue.value = '';
4572                        if (distanceRange) {
4573                            distanceRange.value = '';
4574                        }
4575                    }
4576                    fillDistanceRangeSlider(rangeSlider);
4577                });
4578
4579                // Set initial state
4580                const isChecked = radiusCheckbox.checked;
4581                rangeInput.disabled = !isChecked;
4582                rangeSlider.disabled = !isChecked;
4583                if (isChecked) {
4584                    radiusValue.value = defaultRadius;
4585                    if (distanceRange) {
4586                        distanceRange.value = defaultRadius;
4587                    }
4588                } else {
4589                    radiusValue.value = '';
4590                    if (distanceRange) {
4591                        distanceRange.value = '';
4592                    }
4593                }
4594            }
4595        };
4596
4597        /**
4598         * Initialize the price range slider
4599         * @param {number|string} min_price - Minimum price
4600         * @param {number|string} max_price - Maximum price
4601         * @param {string} unique_id - Unique identifier for this price range slider
4602         * @param {number|null} selectedMinPrice - Selected minimum price
4603         * @param {number|null} selectedMaxPrice - Selected maximum price
4604         */
4605        const initPriceRangeSlider = function (
4606            min_price,
4607            max_price,
4608            unique_id = 'default',
4609            selectedMinPrice = null,
4610            selectedMaxPrice = null
4611        ) {
4612            const fromSlider = document.getElementById(
4613                'fromSlider_' + unique_id
4614            );
4615            const toSlider = document.getElementById('toSlider_' + unique_id);
4616            const rangeWrap = document.querySelector(
4617                `.range-wrap[data-price-range-id="${unique_id}"]`
4618            );
4619
4620            if (!rangeWrap) {
4621                return;
4622            }
4623
4624            const minPriceDisplay = rangeWrap.querySelector('.min-price-range');
4625            const maxPriceDisplay = rangeWrap.querySelector('.max-price-range');
4626
4627            if (
4628                !fromSlider ||
4629                !toSlider ||
4630                !minPriceDisplay ||
4631                !maxPriceDisplay
4632            ) {
4633                return;
4634            }
4635
4636            // Ensure min_price and max_price are numbers
4637            min_price = parseInt(min_price, 10);
4638            max_price = parseInt(max_price, 10);
4639
4640            // Set min and max attributes for both sliders
4641            fromSlider.min = min_price;
4642            fromSlider.max = max_price;
4643            toSlider.min = min_price;
4644            toSlider.max = max_price;
4645
4646            // Set initial values based on selected values or defaults
4647            if (selectedMinPrice !== null && selectedMaxPrice !== null) {
4648                fromSlider.value = selectedMinPrice;
4649                toSlider.value = selectedMaxPrice;
4650            } else {
4651                fromSlider.value = min_price;
4652                toSlider.value = max_price;
4653            }
4654
4655            const formatCurrency = (value) => {
4656                // Get separators from theme options
4657                const thousandSeparator =
4658                    houzez_vars.thousands_separator || ',';
4659                const decimalSeparator =
4660                    houzez_vars.decimal_point_separator || '.';
4661
4662                // Format number with proper separators
4663                let formattedValue = value.toString();
4664
4665                // Split number at decimal point
4666                const parts = formattedValue.split('.');
4667
4668                // Format the whole number part with thousand separators
4669                parts[0] = parts[0].replace(
4670                    /\B(?=(\d{3})+(?!\d))/g,
4671                    thousandSeparator
4672                );
4673
4674                // Join back with decimal part if exists
4675                formattedValue = parts.join(decimalSeparator);
4676
4677                // Add currency symbol based on position
4678                if (houzez_vars.currency_position === 'after') {
4679                    return formattedValue + houzez_vars.currency_symbol;
4680                } else {
4681                    return houzez_vars.currency_symbol + formattedValue;
4682                }
4683            };
4684
4685            // Creates the gradient background effect for the slider
4686            const fillSlider = (
4687                from,
4688                to,
4689                sliderColor,
4690                rangeColor,
4691                controlSlider
4692            ) => {
4693                const rangeDistance = to.max - to.min;
4694                const fromPosition = from.value - to.min;
4695                const toPosition = to.value - to.min;
4696                const isRTL = document.documentElement.dir === 'rtl';
4697                const direction = isRTL ? 'to left' : 'to right';
4698                controlSlider.style.background = `linear-gradient(
4699                    ${direction},
4700                    ${sliderColor} 0%,
4701                    ${sliderColor} ${(fromPosition / rangeDistance) * 100}%,
4702                    ${rangeColor} ${(fromPosition / rangeDistance) * 100}%,
4703                    ${rangeColor} ${(toPosition / rangeDistance) * 100}%,
4704                    ${sliderColor} ${(toPosition / rangeDistance) * 100}%,
4705                    ${sliderColor} 100%)`;
4706            };
4707
4708            // Track which slider was last interacted with
4709            let lastActiveSlider = null;
4710
4711            // Adjusts z-index of sliders for better handle overlap behavior
4712            const setToggleAccessible = (activeSlider = null) => {
4713                const from = parseInt(fromSlider.value, 10);
4714                const to = parseInt(toSlider.value, 10);
4715                const range =
4716                    parseInt(toSlider.max, 10) - parseInt(toSlider.min, 10);
4717                const threshold = range * 0.05; // 5% of range as overlap threshold
4718
4719                // Check if sliders are overlapping or very close
4720                const isOverlapping = Math.abs(to - from) <= threshold;
4721
4722                if (isOverlapping && activeSlider) {
4723                    // When overlapping, the active slider gets higher z-index
4724                    if (activeSlider === 'from') {
4725                        fromSlider.style.zIndex = 2;
4726                        toSlider.style.zIndex = 0;
4727                    } else {
4728                        fromSlider.style.zIndex = 0;
4729                        toSlider.style.zIndex = 2;
4730                    }
4731                } else {
4732                    // Default behavior when not overlapping
4733                    // Min slider has higher z-index when at same position
4734                    if (from === to) {
4735                        fromSlider.style.zIndex = 2;
4736                        toSlider.style.zIndex = 0;
4737                    } else {
4738                        fromSlider.style.zIndex = 1;
4739                        toSlider.style.zIndex = 0;
4740                    }
4741                }
4742            };
4743
4744            const controlFromSlider = () => {
4745                const from = parseInt(fromSlider.value, 10);
4746                const to = parseInt(toSlider.value, 10);
4747
4748                fillSlider(
4749                    fromSlider,
4750                    toSlider,
4751                    '#dce0e0',
4752                    houzez_vars.primary_color,
4753                    toSlider
4754                );
4755
4756                if (from > to) {
4757                    fromSlider.value = to;
4758                } else {
4759                    fromSlider.value = from;
4760                }
4761
4762                // Set this as the active slider
4763                lastActiveSlider = 'from';
4764                setToggleAccessible('from');
4765
4766                // Update display values
4767                minPriceDisplay.textContent = formatCurrency(fromSlider.value);
4768                maxPriceDisplay.textContent = formatCurrency(toSlider.value);
4769            };
4770
4771            const controlToSlider = () => {
4772                const from = parseInt(fromSlider.value, 10);
4773                const to = parseInt(toSlider.value, 10);
4774
4775                fillSlider(
4776                    fromSlider,
4777                    toSlider,
4778                    '#dce0e0',
4779                    houzez_vars.primary_color,
4780                    toSlider
4781                );
4782
4783                if (from <= to) {
4784                    toSlider.value = to;
4785                } else {
4786                    toSlider.value = from;
4787                }
4788
4789                // Set this as the active slider
4790                lastActiveSlider = 'to';
4791                setToggleAccessible('to');
4792
4793                // Update display values
4794                minPriceDisplay.textContent = formatCurrency(fromSlider.value);
4795                maxPriceDisplay.textContent = formatCurrency(toSlider.value);
4796            };
4797
4798            // Add event listeners
4799            fromSlider.addEventListener('input', controlFromSlider);
4800            toSlider.addEventListener('input', controlToSlider);
4801
4802            // Initialize slider appearance
4803            fillSlider(
4804                fromSlider,
4805                toSlider,
4806                '#dce0e0',
4807                houzez_vars.primary_color,
4808                toSlider
4809            );
4810            setToggleAccessible();
4811
4812            // Initialize display values
4813            minPriceDisplay.textContent = formatCurrency(fromSlider.value);
4814            maxPriceDisplay.textContent = formatCurrency(toSlider.value);
4815        };
4816
4817        /**
4818         * Initialize price range sliders with appropriate values based on property status
4819         */
4820        const initPriceRangeSliders = function () {
4821            // Check if any price range sliders exist
4822            if (!$('.range-wrap[data-price-range-id]').length) {
4823                return;
4824            }
4825
4826            // Get min and max values from theme options
4827            const min_price_rent =
4828                parseInt(houzez_vars.search_min_price_range_for_rent, 10) || 0;
4829            const max_price_rent =
4830                parseInt(houzez_vars.search_max_price_range_for_rent, 10) ||
4831                1000000;
4832            const min_price_sale =
4833                parseInt(houzez_vars.search_min_price_range, 10) || 0;
4834            const max_price_sale =
4835                parseInt(houzez_vars.search_max_price_range, 10) || 1000000;
4836
4837            // Check if we have values from search results
4838            let selectedMinPrice = null;
4839            let selectedMaxPrice = null;
4840
4841            if (houzez_vars.get_min_price && houzez_vars.get_max_price) {
4842                selectedMinPrice = parseInt(houzez_vars.get_min_price, 10);
4843                selectedMaxPrice = parseInt(houzez_vars.get_max_price, 10);
4844            }
4845
4846            // Initialize all price range sliders
4847            $('.range-wrap[data-price-range-id]').each(function () {
4848                const unique_id = $(this).data('price-range-id');
4849                const selected_status_adv_search = $('select.status-js').val();
4850
4851                // Update slider min/max values based on property status
4852                if (
4853                    selected_status_adv_search ==
4854                    houzez_vars.for_rent_price_slider
4855                ) {
4856                    initPriceRangeSlider(
4857                        min_price_rent,
4858                        max_price_rent,
4859                        unique_id,
4860                        selectedMinPrice,
4861                        selectedMaxPrice
4862                    );
4863                } else {
4864                    initPriceRangeSlider(
4865                        min_price_sale,
4866                        max_price_sale,
4867                        unique_id,
4868                        selectedMinPrice,
4869                        selectedMaxPrice
4870                    );
4871                }
4872            });
4873
4874            // Handle price slider changes based on status
4875            $('select.status-js').on('change', function () {
4876                let search_status = $(this).val();
4877
4878                // Update all price range sliders
4879                $('.range-wrap[data-price-range-id]').each(function () {
4880                    const unique_id = $(this).data('price-range-id');
4881
4882                    if (search_status == houzez_vars.for_rent_price_slider) {
4883                        initPriceRangeSlider(
4884                            min_price_rent,
4885                            max_price_rent,
4886                            unique_id
4887                        );
4888                    } else {
4889                        initPriceRangeSlider(
4890                            min_price_sale,
4891                            max_price_sale,
4892                            unique_id
4893                        );
4894                    }
4895                });
4896            });
4897
4898            // Handle price slider changes based on status tab
4899            $('.status-tab-js').on('click', function () {
4900                let tab_status = $(this).data('val');
4901
4902                // Update all price range sliders
4903                $('.range-wrap[data-price-range-id]').each(function () {
4904                    const unique_id = $(this).data('price-range-id');
4905
4906                    if (tab_status == houzez_vars.for_rent_price_slider) {
4907                        initPriceRangeSlider(
4908                            min_price_rent,
4909                            max_price_rent,
4910                            unique_id
4911                        );
4912                    } else {
4913                        initPriceRangeSlider(
4914                            min_price_sale,
4915                            max_price_sale,
4916                            unique_id
4917                        );
4918                    }
4919                });
4920            });
4921        };
4922
4923        /**
4924         * Initialize auto-complete for keyword search
4925         */
4926        const initKeywordAutocomplete = function () {
4927            if (houzez_vars.keyword_autocomplete != 0) {
4928                let ajaxCount = 0;
4929                let $dataType = '';
4930
4931                $('body').on(
4932                    'keyup',
4933                    '.houzez-keyword-autocomplete',
4934                    houzez.Core.util.debounce(function () {
4935                        let $this = $(this);
4936                        $dataType = $this.data('type');
4937                        let $form = $this.parents('form');
4938
4939                        let auto_complete_container =
4940                            $form.find('.auto-complete');
4941
4942                        if ($dataType == 'banner') {
4943                            auto_complete_container = $(
4944                                '.auto-complete-banner'
4945                            );
4946                        }
4947
4948                        let keyword = $this.val();
4949
4950                        keyword = $.trim(keyword);
4951                        const currentLength = keyword.length;
4952
4953                        if (currentLength < 2) {
4954                            auto_complete_container.hide();
4955                            return;
4956                        }
4957
4958                        auto_complete_container.fadeIn();
4959
4960                        $.ajax({
4961                            type: 'POST',
4962                            url: houzez.Core.config.ajaxurl,
4963                            data: {
4964                                action: 'houzez_get_auto_complete_search',
4965                                key: keyword,
4966                            },
4967                            beforeSend: function () {
4968                                ajaxCount++;
4969                                if (ajaxCount == 1) {
4970                                    auto_complete_container.html(
4971                                        '<ul class="list-group"><li class="list-group-item"><i class="fa fa-spinner fa-spin fa-fw"></i> ' +
4972                                            houzez_vars.autosearch_text +
4973                                            '</li></ul>'
4974                                    );
4975                                }
4976                            },
4977                            success: function (data) {
4978                                ajaxCount--;
4979                                if (ajaxCount == 0) {
4980                                    auto_complete_container.show();
4981                                    if (data != '') {
4982                                        auto_complete_container
4983                                            .empty()
4984                                            .html(data);
4985                                    }
4986                                }
4987                            },
4988                            error: function (xhr, status, error) {
4989                                ajaxCount--;
4990                                if (ajaxCount == 0) {
4991                                    auto_complete_container.html(
4992                                        '<ul class="list-group"><li class="list-group-item"><i class="fa fa-spinner fa-spin fa-fw"></i> ' +
4993                                            error +
4994                                            '</li></ul>'
4995                                    );
4996                                }
4997                            },
4998                        });
4999                    }, 300)
5000                );
5001
5002                // Handle mousedown on autocomplete items to update input value
5003                // before click events fire (fixes half-map search reading old value)
5004                $(document).on('mousedown', '.auto-complete li', function () {
5005                    let $this = $(this);
5006                    let $input;
5007                    let $container = $this.closest('.auto-complete');
5008
5009                    $input = $container.siblings(
5010                        '.houzez-keyword-autocomplete'
5011                    );
5012
5013                    if ($dataType == 'banner') {
5014                        $input = $('.houzez-keyword-autocomplete.is-banner');
5015                    }
5016
5017                    $input.val($this.data('text'));
5018                });
5019
5020                // Handle click on autocomplete items to hide dropdown
5021                $(document).on('click', '.auto-complete li', function () {
5022                    let $this = $(this);
5023                    let $container = $this.closest('.auto-complete');
5024                    $container.fadeOut();
5025                });
5026
5027                // Handle click on "View All Results" link
5028                $(document).on('click', '.search-result-view', function () {
5029                    $('.auto-complete').fadeOut();
5030                });
5031            }
5032        };
5033
5034        /**
5035         * Handle reset button click to clear all search fields
5036         */
5037        const handleResetSearch = function () {
5038            $('.reset-search-btn').on('click', function (e) {
5039                e.preventDefault();
5040
5041                const $form = $(this).closest('form');
5042
5043                // Reset text inputs and selects
5044                $form
5045                    .find('input[type="text"], input[type="number"], select')
5046                    .each(function () {
5047                        $(this).val('');
5048                    });
5049
5050                // Reset selects with Select2
5051                $form.find('select.selectpicker').each(function () {
5052                    $(this).val('').selectpicker('refresh');
5053                });
5054
5055                // Reset checkboxes and radio buttons, except use_radius
5056                $form
5057                    .find('input[type="checkbox"], input[type="radio"]')
5058                    .not('[name="use_radius"]')
5059                    .prop('checked', false);
5060
5061                // Ensure use_radius remains checked
5062                $form.find('input[name="use_radius"]').prop('checked', true);
5063
5064                // Reset price range sliders
5065                $form.find('.range-slider').each(function () {
5066                    const $rangeWrap = $(this).closest('.range-wrap');
5067                    const fromSlider = $rangeWrap.find(
5068                        '.min-price-range-slider'
5069                    )[0];
5070                    const toSlider = $rangeWrap.find(
5071                        '.max-price-range-slider'
5072                    )[0];
5073
5074                    if (fromSlider && toSlider) {
5075                        fromSlider.value = fromSlider.min;
5076                        toSlider.value = toSlider.max;
5077
5078                        // Trigger input event to update UI
5079                        $(fromSlider).trigger('input');
5080                        $(toSlider).trigger('input');
5081                    }
5082                });
5083
5084                // Reset distance slider
5085                const distanceSlider = $form.find('#radius-range-slider')[0];
5086                const distanceInput = $form.find('#radius-range-value')[0];
5087                const distanceText = $form.find('#radius-range-text')[0];
5088
5089                if (distanceSlider && distanceInput) {
5090                    // Get default radius from radius-range-value input which has the data-default attribute
5091                    const defaultRadius =
5092                        $(distanceInput).data('default') || 50;
5093
5094                    // Reset the slider value
5095                    distanceSlider.value = defaultRadius;
5096
5097                    // Reset the hidden input value (radius-range-value)
5098                    distanceInput.value = defaultRadius;
5099
5100                    // Also reset any direct input field with name="radius"
5101                    $form.find('input[name="radius"]').val(defaultRadius);
5102
5103                    // Reset the text display if it exists
5104                    if (distanceText) {
5105                        distanceText.value = defaultRadius;
5106                    }
5107
5108                    // Update fill
5109                    if (typeof fillDistanceRangeSlider === 'function') {
5110                        fillDistanceRangeSlider(distanceSlider);
5111                    } else {
5112                        // If the function is not directly accessible, use it from the current context
5113                        const rangeDistance =
5114                            distanceSlider.max - distanceSlider.min;
5115                        const position = defaultRadius - distanceSlider.min;
5116
5117                        // Apply the gradient fill manually
5118                        const isRTL = document.documentElement.dir === 'rtl';
5119                        const direction = isRTL ? 'to left' : 'to right';
5120                        distanceSlider.style.background = `linear-gradient(
5121                            ${direction},
5122                            #dce0e0 0%,
5123                            #dce0e0 ${(position / rangeDistance) * 100}%,
5124                            ${houzez_vars.primary_color} ${
5125                            (position / rangeDistance) * 100
5126                        }%,
5127                            ${houzez_vars.primary_color} 100%
5128                        )`;
5129                    }
5130                } else {
5131                    // If slider elements aren't found, still try to reset radius input field
5132                    const defaultRadius = 50;
5133                    $form.find('input[name="radius"]').val(defaultRadius);
5134                }
5135
5136                // Reset hidden fields
5137                $form
5138                    .find('input[type="hidden"]')
5139                    .not(
5140                        '[name="_wp_http_referer"], [name="search-location"], [name="radius"]'
5141                    )
5142                    .val('');
5143
5144                // Clear location fields
5145                $form.find('.location-search input[type="text"]').val('');
5146
5147                // Clear keyword fields
5148                $form.find('.keyword-search input').val('');
5149
5150                // Hide any open dropdowns
5151                $('.search-dropdown-wrap').removeClass('show');
5152
5153                // If using any custom sliders, reset them here
5154
5155                // If this is a half map search, submit the form after reset
5156                const isHalfMapSearch = $('.main-half-map-wrap').length > 0;
5157
5158                if (isHalfMapSearch) {
5159                    // Submit the form after a small delay to ensure all values are properly reset
5160                    setTimeout(function () {
5161                        $form.find('.btn-search').trigger('click');
5162                    }, 100);
5163                    return false;
5164                }
5165
5166                // Don't submit the form - let user click search if they want to
5167                return false;
5168            });
5169        };
5170
5171        /**
5172         * Initialize beds and baths counter functionality
5173         */
5174        const initBedsAndBaths = function () {
5175            /* ------------------------------------------------------------------------ */
5176            /* Beds and baths
5177            /* ------------------------------------------------------------------------ */
5178            const beds_baths = function (btn_action, btn_count, btn_val) {
5179                $('.' + btn_action).on('click', function (e) {
5180                    e.preventDefault();
5181                    e.stopPropagation();
5182                    var current_val = parseInt($('.' + btn_val).val()) || 0;
5183
5184                    if (
5185                        btn_action == 'btn_count_plus' ||
5186                        btn_action == 'btn_beds_plus'
5187                    ) {
5188                        current_val++;
5189                    } else {
5190                        if (current_val == 0) return;
5191                        current_val--;
5192                    }
5193
5194                    $('.' + btn_count).text(current_val);
5195                    $('.' + btn_val).val(current_val);
5196                });
5197            };
5198
5199            // Initialize beds and baths buttons
5200            beds_baths('btn_count_plus', 'baths_count', 'bathrooms');
5201            beds_baths('btn_count_minus', 'baths_count', 'bathrooms');
5202            beds_baths('btn_beds_plus', 'beds_count', 'bedrooms');
5203            beds_baths('btn_beds_minus', 'beds_count', 'bedrooms');
5204            beds_baths('btn_rooms_plus', 'rooms_count', 'rooms');
5205            beds_baths('btn_rooms_minus', 'rooms_count', 'rooms');
5206
5207            // Apply button handler
5208            $('.btn-apply').on('click', function (e) {
5209                e.preventDefault();
5210                $('.advanced-search-v3 .btn-group .dropdown-menu').removeClass(
5211                    'show'
5212                );
5213            });
5214
5215            // Clear buttons handlers
5216            $('.clear-baths').on('click', function (e) {
5217                e.preventDefault();
5218                e.stopPropagation();
5219                $('.baths_count').text('0');
5220                $('.bathrooms').val('');
5221            });
5222
5223            $('.clear-beds').on('click', function (e) {
5224                e.preventDefault();
5225                e.stopPropagation();
5226                $('.beds_count').text('0');
5227                $('.bedrooms').val('');
5228            });
5229
5230            $('.clear-rooms').on('click', function (e) {
5231                e.preventDefault();
5232                e.stopPropagation();
5233                $('.rooms_count').text('0');
5234                $('.rooms').val('');
5235            });
5236
5237            $('.clear-checkboxes').on('click', function (e) {
5238                e.preventDefault();
5239                e.stopPropagation();
5240                $(this)
5241                    .parents('.btn-group')
5242                    .find('input[type="checkbox"]')
5243                    .prop('checked', false)
5244                    .attr('checked', false);
5245            });
5246        };
5247
5248        /**
5249         * Initialize the save search functionality
5250         */
5251        const initSaveSearch = function () {
5252            $('.save_search_click').on('click', function (e) {
5253                e.preventDefault();
5254
5255                var $this = $(this);
5256                let $parent = $this.parents('.save-search-form-wrap');
5257
5258                var search_args = $parent
5259                    .find('input[name="search_args"]')
5260                    .val();
5261                var security = $parent
5262                    .find('input[name="houzez_save_search_ajax"]')
5263                    .val();
5264                var search_URI = $parent.find('input[name="search_URI"]').val();
5265
5266                if (parseInt(houzez.Core.config.userID, 10) === 0) {
5267                    houzez.Favorites.showLoginModal();
5268                } else {
5269                    $.ajax({
5270                        url: houzez.Core.config.ajaxurl,
5271                        data: {
5272                            action: 'houzez_save_search',
5273                            search_args: search_args,
5274                            search_URI: search_URI,
5275                            houzez_save_search_ajax: security,
5276                        },
5277                        method: 'POST',
5278                        dataType: 'JSON',
5279
5280                        beforeSend: function () {
5281                            $this
5282                                .find('.houzez-loader-js')
5283                                .addClass('loader-show');
5284                        },
5285                        success: function (response) {
5286                            if (response.success) {
5287                                $('.save_search_click').attr('disabled', true);
5288                            }
5289                        },
5290                        error: function (xhr, status, error) {
5291                            var err = eval('(' + xhr.responseText + ')');
5292                            console.log(err.Message);
5293                        },
5294                        complete: function () {
5295                            $this
5296                                .find('.houzez-loader-js')
5297                                .removeClass('loader-show');
5298                        },
5299                    });
5300                }
5301            });
5302        };
5303
5304        /**
5305         * Initialize the module
5306         */
5307        const init = function () {
5308            mobileSearchOverlay();
5309            initSearchTabs();
5310            initStatusChangeHandlers();
5311            initPriceRangeSliders();
5312            initKeywordAutocomplete();
5313            handleResetSearch();
5314            initBedsAndBaths();
5315            initDistanceRangeSlider();
5316            initSaveSearch();
5317        };
5318
5319        // Public API
5320        return {
5321            init: init,
5322            mobileSearchOverlay: mobileSearchOverlay,
5323            statusChangeHandler: statusChangeHandler,
5324            initPriceRangeSlider: initPriceRangeSlider,
5325            initSearchTabs: initSearchTabs,
5326            initKeywordAutocomplete: initKeywordAutocomplete,
5327            handleResetSearch: handleResetSearch,
5328            initBedsAndBaths: initBedsAndBaths,
5329            initDistanceRangeSlider: initDistanceRangeSlider,
5330            initSaveSearch: initSaveSearch,
5331        };
5332    })();
5333
5334    /* ------------------------------------------------------------------------ */
5335    /* BLOG
5336    /* ------------------------------------------------------------------------ */
5337    houzez.Blog = (function () {
5338        const resizeMasonryItem = function (item) {
5339            /* Get the grid object, its row-gap, and the size of its implicit rows */
5340            var grid = document.getElementsByClassName('masonry')[0],
5341                rowGap = parseInt(
5342                    window
5343                        .getComputedStyle(grid)
5344                        .getPropertyValue('grid-row-gap')
5345                ),
5346                rowHeight = parseInt(
5347                    window
5348                        .getComputedStyle(grid)
5349                        .getPropertyValue('grid-auto-rows')
5350                );
5351
5352            /*
5353             * Spanning for any brick = S
5354             * Grid's row-gap = G
5355             * Size of grid's implicitly create row-track = R
5356             * Height of item content = H
5357             * Net height of the item = H1 = H + G
5358             * Net height of the implicit row-track = T = G + R
5359             * S = H1 / T
5360             */
5361            var rowSpan = Math.ceil(
5362                (item.querySelector('.masonry-content').getBoundingClientRect()
5363                    .height +
5364                    rowGap) /
5365                    (rowHeight + rowGap)
5366            );
5367
5368            /* Set the spanning as calculated above (S) */
5369            item.style.gridRowEnd = 'span ' + rowSpan;
5370        };
5371
5372        const resizeAllMasonryItems = function () {
5373            // Get all item class objects in one list
5374            var allItems = document.getElementsByClassName('masonry-brick');
5375
5376            /*
5377             * Loop through the above list and execute the spanning function to
5378             * each list-item (i.e. each masonry item)
5379             */
5380            for (var i = 0; i < allItems.length; i++) {
5381                resizeMasonryItem(allItems[i]);
5382            }
5383        };
5384
5385        const waitForImages = function () {
5386            var allItems = document.getElementsByClassName('masonry-brick');
5387            for (var i = 0; i < allItems.length; i++) {
5388                var images = allItems[i].getElementsByTagName('img');
5389                for (var j = 0; j < images.length; j++) {
5390                    images[j].addEventListener('load', function () {
5391                        resizeMasonryItem(this.closest('.masonry-brick'));
5392                    });
5393                    if (images[j].complete) {
5394                        resizeMasonryItem(images[j].closest('.masonry-brick'));
5395                    }
5396                }
5397            }
5398        };
5399
5400        const initMasonry = function () {
5401            /* Resize all the grid items on the load and resize events */
5402            var masonryEvents = ['load', 'resize'];
5403            masonryEvents.forEach(function (event) {
5404                window.addEventListener(event, resizeAllMasonryItems);
5405            });
5406
5407            /* Do a resize once more when all the images finish loading */
5408            waitForImages();
5409        };
5410
5411        const init = function () {
5412            initMasonry();
5413        };
5414
5415        return {
5416            init: init,
5417            initMasonry: initMasonry,
5418        };
5419    })();
5420
5421    /**
5422     * LocationFilter Module
5423     * Handles location filtering functionality
5424     */
5425    houzez.LocationFilter = (function () {
5426        /**
5427         * Handle select filter functionality
5428         * @param {jQuery} $this - jQuery element
5429         * @param {string} dataRef - Data reference
5430         * @param {string} list_num - List number
5431         * @param {string} is_on_load - Whether function is called on load
5432         */
5433        const houzezSelectFilter = function (
5434            $this,
5435            dataRef,
5436            list_num = '',
5437            is_on_load = ''
5438        ) {
5439            let e = $this.data('target');
5440            let i = $this.find(':selected').data('ref');
5441
5442            if (is_on_load == 'yes') {
5443                e = list_num;
5444                i = dataRef;
5445            }
5446
5447            $('select.' + e).val('');
5448
5449            if (i == null) {
5450                $('select.' + e)
5451                    .find('option')
5452                    .each(function () {
5453                        $(this).removeAttr('disabled hidden');
5454                    });
5455            } else {
5456                $('select.' + e)
5457                    .find('option')
5458                    .each(function () {
5459                        const e = $(this).data('belong');
5460                        const t = $(this).val();
5461
5462                        if (i != e && t != '') {
5463                            $(this).prop('disabled', true);
5464                            $(this).prop('hidden', true);
5465                        } else {
5466                            $(this).prop('disabled', false);
5467                            $(this).prop('hidden', false);
5468                        }
5469                    });
5470            }
5471            $('select.' + e).selectpicker('refresh');
5472        };
5473
5474        /**
5475         * Initialize location filters on page load
5476         */
5477        const initLocationFilters = function () {
5478            if (
5479                $('.houzez-search-form-js').length > 0 ||
5480                $('#location').length > 0
5481            ) {
5482                let countryRef, stateRef, cityRef, areasRef;
5483
5484                if (
5485                    typeof is_edit_property !== 'undefined' &&
5486                    is_edit_property
5487                ) {
5488                    countryRef = $('#country').data('country');
5489                    stateRef = $('#countyState').data('state');
5490                    cityRef = $('#city').data('city');
5491                    areasRef = $('#neighborhood').data('area');
5492                } else {
5493                    countryRef = houzez_vars.s_country;
5494                    stateRef = houzez_vars.s_state;
5495                    cityRef = houzez_vars.s_city;
5496                    areasRef = houzez_vars.s_areas;
5497                }
5498
5499                // Handle country filter
5500                if (
5501                    ($('.houzez-country-js').length > 0 ||
5502                        $('#country').length > 0) &&
5503                    countryRef != ''
5504                ) {
5505                    const countryFilter = $('.houzezCountryFilter');
5506                    houzezSelectFilter(
5507                        countryFilter,
5508                        countryRef,
5509                        'houzezSecondList',
5510                        'yes'
5511                    );
5512
5513                    $(window).on('load', function () {
5514                        $('.houzezSecondList').val(stateRef);
5515                        $('select.houzezSecondList').selectpicker('refresh');
5516                    });
5517                }
5518
5519                // Handle state filter
5520                if (
5521                    ($('.houzez-state-js').length > 0 ||
5522                        $('#countyState').length > 0) &&
5523                    stateRef != ''
5524                ) {
5525                    const stateFilter = $('.houzezStateFilter');
5526                    houzezSelectFilter(
5527                        stateFilter,
5528                        stateRef,
5529                        'houzezThirdList',
5530                        'yes'
5531                    );
5532
5533                    $(window).on('load', function () {
5534                        $('.houzezThirdList').val(cityRef);
5535                        $('select.houzezThirdList').selectpicker('refresh');
5536                    });
5537                }
5538
5539                // Handle city filter
5540                if (
5541                    ($('.houzez-city-js').length > 0 ||
5542                        $('#city').length > 0) &&
5543                    cityRef != ''
5544                ) {
5545                    const cityFilter = $('.houzezCityFilter');
5546                    houzezSelectFilter(
5547                        cityFilter,
5548                        cityRef,
5549                        'houzezFourthList',
5550                        'yes'
5551                    );
5552
5553                    $(window).on('load', function () {
5554                        $('.houzezFourthList').val(areasRef);
5555                        $('select.houzezFourthList').selectpicker('refresh');
5556                    });
5557                }
5558            }
5559        };
5560
5561        /**
5562         * Initialize the module
5563         */
5564        const init = function () {
5565            // Attach change event handler
5566            $('.houzezSelectFilter').on('change', function () {
5567                houzezSelectFilter($(this));
5568            });
5569
5570            // Initialize location filters
5571            initLocationFilters();
5572        };
5573
5574        // Public API
5575        return {
5576            init: init,
5577            houzezSelectFilter: houzezSelectFilter,
5578        };
5579    })();
5580
5581    /**
5582     * CurrencySwitcher Module
5583     * Handles currency switching functionality
5584     */
5585    houzez.CurrencySwitcher = (function () {
5586        /**
5587         * Process currency switch
5588         * @param {string} selectedCurrencyCode - The selected currency code
5589         */
5590        const processCurrencySwitch = function (selectedCurrencyCode) {
5591            if (!selectedCurrencyCode) {
5592                console.warn('No currency code provided');
5593                return;
5594            }
5595
5596            // Update hidden input value
5597            $('#houzez-switch-to-currency').val(selectedCurrencyCode);
5598
5599            // Show processing modal
5600            houzez.Core.util.processingModal(
5601                houzez.Core.config.processing_text
5602            );
5603
5604            // Make AJAX request
5605            $.ajax({
5606                url: houzez.Core.config.ajaxurl,
5607                dataType: 'JSON',
5608                method: 'POST',
5609                data: {
5610                    action: 'houzez_currency_converter',
5611                    currency_converter: selectedCurrencyCode,
5612                },
5613                success: function (response) {
5614                    if (response.success) {
5615                        window.location.reload(true);
5616                    } else {
5617                        houzez.Core.util.processingModalClose();
5618                        console.warn('Currency conversion failed:', response);
5619
5620                        // Show error message if provided
5621                        if (response.msg) {
5622                            const $messages = $(
5623                                '.hz-currency-switcher-messages'
5624                            );
5625                            houzez.Core.util.showError($messages, response.msg);
5626                        }
5627                    }
5628                },
5629                error: function (xhr, status, error) {
5630                    houzez.Core.util.processingModalClose();
5631                    console.error('Currency switch error:', error);
5632
5633                    // Show generic error message
5634                    const $messages = $('.hz-currency-switcher-messages');
5635                    houzez.Core.util.showError(
5636                        $messages,
5637                        'Error processing currency switch'
5638                    );
5639                },
5640            });
5641        };
5642
5643        /**
5644         * Initialize currency switcher functionality
5645         */
5646        const initCurrencySwitcher = function () {
5647            const $currencySwitcherList = $('#hz-currency-switcher-list');
5648
5649            if ($currencySwitcherList.length === 0) {
5650                return;
5651            }
5652
5653            // Handle currency selection
5654            $currencySwitcherList.on('click', 'li', function (e) {
5655                e.preventDefault();
5656                const selectedCurrencyCode = $(this).data('currency-code');
5657
5658                if (selectedCurrencyCode) {
5659                    processCurrencySwitch(selectedCurrencyCode);
5660                }
5661            });
5662        };
5663
5664        /**
5665         * Initialize the module
5666         */
5667        const init = function () {
5668            initCurrencySwitcher();
5669        };
5670
5671        // Public API
5672        return {
5673            init: init,
5674            processCurrencySwitch: processCurrencySwitch,
5675        };
5676    })();
5677
5678    /**
5679     * AreaSwitcher Module
5680     * Handles area unit switching functionality
5681     */
5682    houzez.AreaSwitcher = (function () {
5683        /**
5684         * Process area unit switch
5685         * @param {string} selectedAreaCode - The selected area unit code
5686         */
5687        const processAreaSwitch = function (selectedAreaCode) {
5688            if (!selectedAreaCode) {
5689                console.warn('No area code provided');
5690                return;
5691            }
5692
5693            // Update hidden input value
5694            $('#houzez-switch-to-area').val(selectedAreaCode);
5695
5696            // Show processing modal
5697            houzez.Core.util.processingModal(
5698                houzez.Core.config.processing_text
5699            );
5700
5701            // Make AJAX request
5702            $.ajax({
5703                url: houzez.Core.config.ajaxurl,
5704                dataType: 'JSON',
5705                method: 'POST',
5706                data: {
5707                    action: 'houzez_switch_area',
5708                    switch_to_area: selectedAreaCode,
5709                },
5710                success: function (response) {
5711                    if (response.success) {
5712                        window.location.reload(true);
5713                    } else {
5714                        houzez.Core.util.processingModalClose();
5715                        console.warn('Area unit conversion failed:', response);
5716
5717                        // Show error message if provided
5718                        if (response.msg) {
5719                            const $messages = $('.hz-area-switcher-messages');
5720                            houzez.Core.util.showError($messages, response.msg);
5721                        }
5722                    }
5723                },
5724                error: function (xhr, status, error) {
5725                    houzez.Core.util.processingModalClose();
5726                    console.error('Area unit switch error:', error);
5727
5728                    // Show generic error message
5729                    const $messages = $('.hz-area-switcher-messages');
5730                    houzez.Core.util.showError(
5731                        $messages,
5732                        'Error processing area unit switch'
5733                    );
5734                },
5735            });
5736        };
5737
5738        /**
5739         * Initialize area switcher functionality
5740         */
5741        const initAreaSwitcher = function () {
5742            const $areaSwitcherList = $('#area-switcher-list-js');
5743
5744            if ($areaSwitcherList.length === 0) {
5745                return;
5746            }
5747
5748            // Handle area unit selection
5749            $areaSwitcherList.on('click', 'li', function (e) {
5750                e.preventDefault();
5751                const selectedAreaCode = $(this).data('area-code');
5752
5753                if (selectedAreaCode) {
5754                    processAreaSwitch(selectedAreaCode);
5755                }
5756            });
5757        };
5758
5759        /**
5760         * Initialize the module
5761         */
5762        const init = function () {
5763            initAreaSwitcher();
5764        };
5765
5766        // Public API
5767        return {
5768            init: init,
5769            processAreaSwitch: processAreaSwitch,
5770        };
5771    })();
5772
5773    /**
5774     * Houzez Elementor Mobile Menu Module
5775     * Handles Elementor mobile menu functionality
5776     */
5777    houzez.ElementorMobileMenu = (function () {
5778        const $win = $(window);
5779
5780        /* ------------------------------------------------------------------------ */
5781        /*  Elementor Mobile menu trigger
5782        /* ------------------------------------------------------------------------ */
5783        const mobileMenuTrigger = function () {
5784            $('.houzez-nav-menu-main-mobile-wrap .houzez-menu-toggle').on(
5785                'click',
5786                function (e) {
5787                    $(
5788                        '.houzez-nav-menu-main-mobile-wrap .navbar-nav, .houzez-nav-menu-main-mobile-wrap .houzez-menu-toggle'
5789                    ).toggleClass('houzez-nav-menu-active');
5790                }
5791            );
5792        };
5793        /**
5794         * Adjust mega menu positioning
5795         */
5796        const adjustMegaMenu = function (
5797            megaMenuSelector,
5798            containerSelector,
5799            fullwidthClass,
5800            customWidthClass,
5801            customWidthPx
5802        ) {
5803            jQuery(megaMenuSelector).each(function () {
5804                var $megamenu = jQuery(this);
5805                var windowWidth = jQuery(window).width();
5806                var isRTL = jQuery('html').attr('dir') === 'rtl';
5807
5808                // Cache common selections
5809                var $fullwidthContainer = $megamenu.closest(fullwidthClass);
5810                var $customWidthContainer = $megamenu.closest(customWidthClass);
5811                var $container = $megamenu.closest(containerSelector);
5812                var $navItem = $megamenu.closest('.nav-item');
5813
5814                // Reset inline styles
5815                $megamenu.css({ left: '', right: '', width: '' });
5816
5817                // Case 1: Full viewport width
5818                if ($fullwidthContainer.length > 0) {
5819                    $megamenu.css('width', '100vw');
5820
5821                    // Store current position
5822                    var originalPosition = $megamenu.css('position');
5823
5824                    // Temporarily set to absolute to get correct offset
5825                    if (originalPosition === 'fixed') {
5826                        $megamenu.css('position', 'absolute');
5827                    }
5828
5829                    var offset = $megamenu.offset();
5830
5831                    // Remove inline position style to let CSS take over
5832                    if (originalPosition === 'fixed') {
5833                        $megamenu.css('position', '');
5834                    }
5835
5836                    if (offset && typeof offset.left === 'number') {
5837                        var position = offset.left * -1;
5838                        if (isRTL) {
5839                            $megamenu.css('right', -position + 'px');
5840                        } else {
5841                            $megamenu.css('left', position + 'px');
5842                        }
5843                    }
5844                }
5845                // Case 2: Custom width
5846                else if ($customWidthContainer.length > 0) {
5847                    $megamenu.css('width', customWidthPx + 'px');
5848                    if ($navItem.length > 0) {
5849                        var menuItemOffset = $navItem.offset().left;
5850                        var parentOffset = $megamenu.parent().offset();
5851                        parentOffset = parentOffset ? parentOffset.left : 0;
5852                        var position = menuItemOffset - parentOffset;
5853
5854                        // Adjust if the dropdown goes outside the window
5855                        if (menuItemOffset + customWidthPx > windowWidth) {
5856                            position -= customWidthPx - $navItem.width();
5857                        }
5858
5859                        if (isRTL) {
5860                            $megamenu.css('right', -position + 'px');
5861                        } else {
5862                            $megamenu.css('left', position + 'px');
5863                        }
5864                    }
5865                }
5866                // Case 3: Use the nearest container's width
5867                else if ($container.length > 0) {
5868                    var containerWidth = $container.width();
5869                    $megamenu.css('width', containerWidth);
5870
5871                    var containerOffsetObj = $container.offset();
5872                    var navItemOffsetObj = $navItem.offset();
5873
5874                    if (containerOffsetObj && navItemOffsetObj) {
5875                        var containerOffset = containerOffsetObj.left;
5876                        var megamenuOffset = navItemOffsetObj.left;
5877                        var position = containerOffset - megamenuOffset;
5878
5879                        if (isRTL) {
5880                            $megamenu.css('right', -position + 'px');
5881                        } else {
5882                            $megamenu.css('left', position + 'px');
5883                        }
5884                    }
5885                }
5886            });
5887        };
5888
5889        /**
5890         * Set full width menu element
5891         */
5892        const setMenuFullWidthEle = function () {
5893            if ($('#houzez_toggle').length > 0) {
5894                var leftDistance = $('#houzez_toggle').offset().left;
5895                $('.houzez-nav-mobile-menu-fullwidth .main-mobile-nav').css(
5896                    'left',
5897                    'calc(50vw - ' + leftDistance + 'px)'
5898                );
5899            }
5900        };
5901
5902        /**
5903         * Initialize all UI-related functionality
5904         */
5905        const init = function () {
5906            // Initialize menu adjustments
5907            $win.on('load', function () {
5908                adjustMegaMenu(
5909                    '.houzez-elementor-menu .dropdown-menu.megamenu',
5910                    '.e-con-inner',
5911                    '.menu-item-design-full-width',
5912                    '.menu-item-design-custom-size',
5913                    200
5914                );
5915                setTimeout(setMenuFullWidthEle, 500);
5916            });
5917
5918            // Window resize events
5919            $win.resize(function () {
5920                adjustMegaMenu(
5921                    '.houzez-elementor-menu .dropdown-menu.megamenu',
5922                    '.e-con-inner',
5923                    '.menu-item-design-full-width',
5924                    '.menu-item-design-custom-size',
5925                    200
5926                );
5927                setMenuFullWidthEle();
5928            });
5929
5930            mobileMenuTrigger();
5931        };
5932
5933        // Public API
5934        return {
5935            init: init,
5936            adjustMegaMenu: adjustMegaMenu,
5937            mobileMenuTrigger: mobileMenuTrigger,
5938        };
5939    })();
5940
5941    /**
5942     * Property Preview Module
5943     * Handles property preview functionality
5944     */
5945    houzez.PropertyPreview = (function () {
5946        const init = function () {
5947            // Remove any existing handlers first to prevent duplicates
5948            $(document).off('click.houzezPreview', '.hz-show-lightbox-js');
5949
5950            // Use event delegation with namespaced event for proper cleanup
5951            $(document).on(
5952                'click.houzezPreview',
5953                '.hz-show-lightbox-js',
5954                function (e) {
5955                    e.preventDefault();
5956                    e.stopPropagation();
5957
5958                    // Hide all tooltips when preview is clicked
5959                    houzez.Core.hideAllTooltips();
5960
5961                    var listing_id = $(this).data('listid');
5962                    var $parents = $(this).parents('.item-wrap');
5963                    var preview_loader = $parents.find('.preview_loader');
5964
5965                    $.ajax({
5966                        type: 'post',
5967                        url: houzez.Core.config.ajaxurl,
5968                        data: {
5969                            action: 'load_lightbox_content',
5970                            listing_id: listing_id,
5971                        },
5972                        beforeSend: function () {
5973                            preview_loader
5974                                .empty()
5975                                .append(
5976                                    '' +
5977                                        '<div class="houzez-overlay-loading">' +
5978                                        '<div class="overlay-placeholder">' +
5979                                        '<div class="loader-ripple spinner">' +
5980                                        '<div class="bounce1"></div>' +
5981                                        '<div class="bounce2"></div>' +
5982                                        '<div class="bounce3"></div>' +
5983                                        '</div>' +
5984                                        '</div>' +
5985                                        '</div>'
5986                                );
5987                        },
5988                        complete: function () {
5989                            preview_loader.empty();
5990                        },
5991                        success: function (response) {
5992                            $('#hz-listing-model-content').html(response);
5993
5994                            // Bootstrap 5 modal initialization
5995                            var modalElement = document.getElementById(
5996                                'houzez-listing-lightbox'
5997                            );
5998                            if (modalElement) {
5999                                // Check if modal instance already exists
6000                                var existingModal =
6001                                    bootstrap.Modal.getInstance(modalElement);
6002                                if (existingModal) {
6003                                    existingModal.show();
6004                                } else {
6005                                    // Create new modal instance with proper options
6006                                    var myModal = new bootstrap.Modal(
6007                                        modalElement,
6008                                        {
6009                                            backdrop: true,
6010                                            keyboard: true,
6011                                            focus: true,
6012                                        }
6013                                    );
6014                                    myModal.show();
6015                                }
6016                            }
6017
6018                            //$('.lightbox-slider').slick('unslick');
6019
6020                            $('.lightbox-slider')
6021                                .not('.slick-initialized')
6022                                .slick({
6023                                    rtl: houzez.Core.config.houzez_rtl,
6024                                    lazyLoad: 'ondemand',
6025                                    infinite: true,
6026                                    speed: 300,
6027                                    slidesToShow: 1,
6028                                    arrows: true,
6029                                    adaptiveHeight: true,
6030                                });
6031
6032                            $('#houzez-listing-lightbox').on(
6033                                'shown.bs.modal',
6034                                function (e) {
6035                                    $('.lightbox-slider').slick('setPosition');
6036                                }
6037                            );
6038
6039                            $('#houzez-listing-lightbox').on(
6040                                'hide.bs.modal',
6041                                function (e) {
6042                                    $('.lightbox-slider').slick('unslick');
6043                                }
6044                            );
6045
6046                            $('.btn-expand').on('click', function () {
6047                                $('.lightbox-gallery-wrap').toggleClass(
6048                                    'lightbox-gallery-full-wrap'
6049                                );
6050                                $('.lightbox-slider').slick('setPosition');
6051                            });
6052
6053                            $('.btn-email').on('click', function () {
6054                                $('.lightbox-form-wrap').toggleClass(
6055                                    'lightbox-form-wrap-show'
6056                                );
6057                            });
6058
6059                            houzez.Core.reinitializeAjaxFunctions();
6060                        },
6061                        error: function (xhr, status, error) {
6062                            console.log(error);
6063                        },
6064                    });
6065                }
6066            );
6067        };
6068
6069        // Public API
6070        return {
6071            init: init,
6072        };
6073    })();
6074
6075    /* ------------------------------------------------------------------------ */
6076    /*  Mobile Navigation
6077    /* ------------------------------------------------------------------------ */
6078    houzez.MobileNav = (function () {
6079        const initMobileNav = function () {};
6080
6081        const setupToggleButtons = function () {};
6082
6083        const init = function () {
6084            initMobileNav();
6085            setupToggleButtons();
6086        };
6087
6088        return {
6089            init: init,
6090        };
6091    })();
6092
6093    /* ------------------------------------------------------------------------ */
6094    /*  Top Banner Full Screen
6095    /* ------------------------------------------------------------------------ */
6096    houzez.TopBannerFullScreen = (function () {
6097        const getWindowHeight = function () {
6098            return $(window).innerHeight();
6099        };
6100
6101        const setTopBannerFullScreen = function () {
6102            var totalTopBarsHeight = 0;
6103            var searchH = 0;
6104            var topBarH = 0;
6105            var totalBannerHeight = 0;
6106            var window_height = $(window).innerHeight();
6107            var admin_bar_height = houzez.Core.config.wpadminbar_height;
6108            var topBarB = $('.top-bar-wrap');
6109            var header_area = $('.header-main-wrap');
6110            if (!header_area.length) {
6111                header_area = $('#header-hz-elementor');
6112            }
6113            var header_area_height = header_area.outerHeight();
6114            var advanced_search_nav = $('.advanced-search-nav');
6115            var advanced_search_nav_height = advanced_search_nav.outerHeight();
6116            searchH = window_height - header_area_height;
6117
6118            if (header_area.hasClass('header-transparent-wrap')) {
6119                if (topBarB.length) {
6120                    topBarH = topBarB.outerHeight();
6121                }
6122                totalBannerHeight =
6123                    getWindowHeight() - (topBarH + admin_bar_height);
6124            } else {
6125                if (
6126                    header_area.length &&
6127                    advanced_search_nav.length &&
6128                    !advanced_search_nav.hasClass('search-hidden')
6129                ) {
6130                    totalTopBarsHeight =
6131                        parseInt(header_area_height) +
6132                        parseInt(advanced_search_nav_height);
6133                } else if (header_area.length) {
6134                    totalTopBarsHeight = parseInt(header_area_height);
6135                }
6136                totalBannerHeight =
6137                    getWindowHeight() - (totalTopBarsHeight + admin_bar_height);
6138            }
6139
6140            $('.top-banner-wrap-fullscreen').css('height', totalBannerHeight);
6141        };
6142
6143        const init = function () {
6144            if (
6145                !houzez.Core.config.houzez_is_splash &&
6146                $('.top-banner-wrap-fullscreen').length > 0
6147            ) {
6148                setTopBannerFullScreen();
6149
6150                $(window).on('resize', function () {
6151                    setTopBannerFullScreen();
6152                });
6153            }
6154        };
6155
6156        return {
6157            init: init,
6158        };
6159    })();
6160
6161    /**
6162     * Reviews Module
6163     * Handles property reviews functionality
6164     */
6165    houzez.Reviews = (function () {
6166        // Like/dislike handler for reviews
6167        const review_likes = function () {
6168            $(document).on('click', '.hz-like-dislike-js', function (e) {
6169                e.preventDefault();
6170                var $this = $(this);
6171                var $parent = $this.parents('.likes-container-js');
6172
6173                if ($this.hasClass('already-voted')) {
6174                    $parent.find('.vote-msg').text($this.data('msg')).show();
6175                    setTimeout(function () {
6176                        $parent.find('.vote-msg').hide();
6177                    }, 3000);
6178                } else {
6179                    var review_id = $this.data('id');
6180                    var type = $this.data('type');
6181
6182                    $.ajax({
6183                        type: 'post',
6184                        url: houzez.Core.config.ajaxurl,
6185                        dataType: 'JSON',
6186                        data: {
6187                            action: 'reviews_likes_dislikes',
6188                            type: type,
6189                            review_id: review_id,
6190                        },
6191                        beforeSend: function () {
6192                            $parent.find('.vote-msg').empty();
6193                            $parent
6194                                .find('.houzez-loader-js')
6195                                .addClass('loader-show');
6196                            if (type == 'likes') {
6197                                $('.review-dislike-button a').removeClass(
6198                                    'already-voted'
6199                                );
6200                            } else if (type == 'dislikes') {
6201                                $('.review-like-button a').removeClass(
6202                                    'already-voted'
6203                                );
6204                            }
6205                        },
6206                        success: function (response) {
6207                            if (response.success) {
6208                                $parent
6209                                    .find('.likes-count')
6210                                    .text(response.data.likes);
6211                                $parent
6212                                    .find('.dislikes-count')
6213                                    .text(response.data.dislikes);
6214                                $parent
6215                                    .find('.vote-msg')
6216                                    .text(response.data.message)
6217                                    .show();
6218                            } else {
6219                                $parent
6220                                    .find('.vote-msg')
6221                                    .text(response.data.message)
6222                                    .show();
6223                            }
6224                            setTimeout(function () {
6225                                $parent.find('.vote-msg').hide();
6226                            }, 3000);
6227                            $this.addClass('already-voted');
6228                        },
6229                        error: function (xhr, status, error) {
6230                            var err = eval('(' + xhr.responseText + ')');
6231                            console.log(err.Message);
6232                        },
6233                        complete: function () {
6234                            $parent
6235                                .find('.houzez-loader-js')
6236                                .removeClass('loader-show');
6237                        },
6238                    });
6239                }
6240            });
6241        };
6242
6243        // AJAX load reviews
6244        const listing_review_ajax = function (sortby, listing_id, paged) {
6245            var review_container = $('#houzez_reviews_container');
6246            var review_post_type = $('input[name="review_post_type"]').val();
6247
6248            $.ajax({
6249                type: 'post',
6250                url: houzez.Core.config.ajaxurl,
6251                data: {
6252                    action: 'houzez_ajax_review',
6253                    sortby: sortby,
6254                    listing_id: listing_id,
6255                    review_post_type: review_post_type,
6256                    paged: paged,
6257                },
6258                beforeSend: function () {
6259                    review_container
6260                        .empty()
6261                        .append(
6262                            '<div id="houzez-map-loading">' +
6263                                '<div class="mapPlaceholder">' +
6264                                '<div class="loader-ripple spinner">' +
6265                                '<div class="bounce1"></div>' +
6266                                '<div class="bounce2"></div>' +
6267                                '<div class="bounce3"></div>' +
6268                                '</div>' +
6269                                '</div>' +
6270                                '</div>'
6271                        );
6272                    $('html, body').animate(
6273                        {
6274                            scrollTop:
6275                                $('#property-review-wrap').offset().top - 50,
6276                        },
6277                        'slow'
6278                    );
6279                },
6280                success: function (data) {
6281                    review_container.empty();
6282                    review_container.html(data);
6283                    review_likes();
6284                },
6285                error: function (xhr, status, error) {
6286                    var err = eval('(' + xhr.responseText + ')');
6287                    console.log(err.Message);
6288                },
6289                complete: function () {},
6290            });
6291        };
6292
6293        // Submit review handler
6294        const submitReview = function () {
6295            $(document).on('click', '#submit-review', function (e) {
6296                e.preventDefault();
6297                var $this = $(this);
6298                var $form = $this.parents('form');
6299                var $messages = $form.find('.form_messages');
6300                var $is_bottom = $('.is_bottom').val();
6301
6302                if ($is_bottom == 'bottom') {
6303                    $messages = $form.find('.form_messages');
6304                }
6305                $messages.empty();
6306
6307                $.ajax({
6308                    type: 'post',
6309                    url: houzez.Core.config.ajaxurl,
6310                    data: $form.serialize(),
6311                    dataType: 'JSON',
6312                    beforeSend: function () {
6313                        $this.attr('disabled', true);
6314                        $this.find('.houzez-loader-js').addClass('loader-show');
6315                    },
6316                    success: function (response) {
6317                        if (response.success) {
6318                       
6318     // Clear form fields
6319                            $form
6320                                .find(
6321                                    'input[name="name"], input[name="mobile"], input[name="email"]'
6322                                )
6323                                .val('');
6324                            $form.find('textarea').val('');
6325
6326                            // Show success message
6327                            houzez.Core.util.showSuccess(
6328                                $messages,
6329                                response.data.message
6330                            );
6331                        } else {
6332                            // Show error message
6333                            houzez.Core.util.showError(
6334                                $messages,
6335                                response.data.message
6336                            );
6337                        }
6338
6339                        // Reset captcha (supports both reCaptcha and Turnstile)
6340                        houzez.Core.util.resetCaptcha($form);
6341
6342                        // Handle redirection if needed
6343                        if (
6344                            houzez_vars.agent_redirection != '' &&
6345                            response.success
6346                        ) {
6347                            setTimeout(function () {
6348                                window.location.replace(
6349                                    houzez_vars.agent_redirection
6350                                );
6351                            }, 500);
6352                        }
6353                    },
6354                    error: function (xhr, status, error) {
6355                        var err = eval('(' + xhr.responseText + ')');
6356                        console.log(err.Message);
6357                    },
6358                    complete: function () {
6359                        $this.attr('disabled', false);
6360                        $this
6361                            .find('.houzez-loader-js')
6362                            .removeClass('loader-show');
6363                    },
6364                });
6365            });
6366        };
6367
6368        // Sort and pagination handlers
6369        const sortAndPaginate = function () {
6370            $(document).on('change', '#sort_review', function () {
6371                var sortby = $(this).val();
6372                var listing_id = $('#review_listing_id').val();
6373                var paged = 1;
6374                $('#review_paged').val(paged);
6375                $('#review_prev').attr('disabled', true);
6376                $('#review_next').attr('disabled', false);
6377                listing_review_ajax(sortby, listing_id, paged);
6378            });
6379
6380            $(document).on('click', '#review_next', function (e) {
6381                e.preventDefault();
6382                $('#review_prev').removeAttr('disabled');
6383                var sortby = $('#sort_review').val();
6384                var total_pages = $('#total_pages').val();
6385                var listing_id = $('#review_listing_id').val();
6386                var paged = $('#review_paged').val();
6387                paged = Number(paged) + 1;
6388                $('#review_paged').val(paged);
6389                if (paged == total_pages) {
6390                    $(this).attr('disabled', true);
6391                }
6392                listing_review_ajax(sortby, listing_id, paged);
6393            });
6394
6395            $(document).on('click', '#review_prev', function (e) {
6396                e.preventDefault();
6397                $('#review_next').removeAttr('disabled');
6398                var sortby = $('#sort_review').val();
6399                var listing_id = $('#review_listing_id').val();
6400                var paged = $('#review_paged').val();
6401                paged = Number(paged) - 1;
6402                $('#review_paged').val(paged);
6403                if (paged <= 1) {
6404                    $(this).attr('disabled', true);
6405                }
6406                listing_review_ajax(sortby, listing_id, paged);
6407            });
6408        };
6409
6410        // Initialize reviews module
6411        const init = function () {
6412            review_likes();
6413            submitReview();
6414            sortAndPaginate();
6415        };
6416
6417        // Public API
6418        return {
6419            init: init,
6420            review_likes: review_likes,
6421            listing_review_ajax: listing_review_ajax,
6422        };
6423    })();
6424
6425    houzez.Parallax = (function () {
6426        // closure vars
6427        let $els;
6428        const speedDefault = 0.5;
6429        let ticking = false;
6430
6431        const init = function () {
6432            $els = $('.houzez-parallax');
6433            if ($els.length === 0) {
6434                return;
6435            }
6436
6437            _setBackgrounds();
6438            _bindEvents();
6439            _update(); // position backgrounds on load
6440        };
6441
6442        const _setBackgrounds = function () {
6443            $els.each(function () {
6444                const $el = $(this);
6445                const src = $el.data('parallax-bg-image');
6446                if (src) {
6447                    $el.css({
6448                        'background-image': `url("${src}")`,
6449                        'background-size': 'cover',
6450                        'background-position': 'center center',
6451                        'background-repeat': 'no-repeat',
6452                    });
6453                }
6454            });
6455        };
6456
6457        const _bindEvents = function () {
6458            $(window).on('scroll', _onScroll);
6459        };
6460
6461        const _onScroll = function () {
6462            if (!ticking) {
6463                window.requestAnimationFrame(function () {
6464                    _update();
6465                    ticking = false;
6466                });
6467                ticking = true;
6468            }
6469        };
6470
6471        const _update = function () {
6472            const scrollY = $(window).scrollTop();
6473
6474            $els.each(function () {
6475                const $el = $(this);
6476                const speed =
6477                    parseFloat($el.data('parallax-speed')) || speedDefault;
6478                const top = $el.offset().top;
6479                const offset = scrollY - top;
6480
6481                $el.css('background-position', `center ${offset * speed}px`);
6482            });
6483        };
6484
6485        // Public API
6486        return {
6487            init: init,
6488        };
6489    })();
6490
6491    // Social login module
6492    houzez.SocialLogin = (function () {
6493        const initFacebookLogin = function () {
6494            $('.hz-facebook-login').on('click', function () {
6495                var current = $(this);
6496                loginViaFacebook(current);
6497            });
6498        };
6499
6500        const loginViaFacebook = function (current) {
6501            var $messages = $('.hz-social-messages');
6502            const config = houzez.Core.config;
6503
6504            $.ajax({
6505                type: 'POST',
6506                url: config.ajaxurl,
6507                dataType: 'json',
6508                data: {
6509                    action: 'houzez_facebook_login_oauth',
6510                },
6511                beforeSend: function () {
6512                    houzez.Core.util.showSuccess(
6513                        $messages,
6514                        config.login_loading
6515                    );
6516                    current.find('.houzez-loader-js').addClass('loader-show');
6517                },
6518                complete: function () {
6519                    current
6520                        .find('.houzez-loader-js')
6521                        .removeClass('loader-show');
6522                },
6523                success: function (response) {
6524                    if (response.success) {
6525                        window.location.replace(response.url);
6526                    } else {
6527                        houzez.Core.util.showError($messages, response.message);
6528                    }
6529                },
6530                error: function (xhr, status, error) {
6531                    console.error('Facebook login error: ' + error);
6532                    houzez.Core.util.showError(
6533                        $messages,
6534                        'Error processing login'
6535                    );
6536                },
6537            });
6538        };
6539
6540        const initGoogleLogin = function () {
6541            $('.hz-google-login').on('click', function () {
6542                var current = $(this);
6543                loginViaGoogle(current);
6544            });
6545        };
6546
6547        const loginViaGoogle = function (current) {
6548            var $messages = $('#hz-login-messages');
6549            const config = houzez.Core.config;
6550
6551            $.ajax({
6552                type: 'POST',
6553                url: config.ajaxurl,
6554                data: {
6555                    action: 'houzez_google_login_oauth',
6556                },
6557                beforeSend: function () {
6558                    houzez.Core.util.showSuccess(
6559                        $messages,
6560                        config.login_loading
6561                    );
6562                    current.find('.houzez-loader-js').addClass('loader-show');
6563                },
6564                complete: function () {
6565                    current
6566                        .find('.houzez-loader-js')
6567                        .removeClass('loader-show');
6568                },
6569                success: function (data) {
6570                    window.location.replace(data);
6571                },
6572                error: function (xhr, status, error) {
6573                    console.error('Google login error: ' + error);
6574                    houzez.Core.util.showError($messages, error);
6575                },
6576            });
6577        };
6578
6579        const initLinkAccount = function () {
6580            $('#houzez-link-account').on('click', function (e) {
6581                e.preventDefault();
6582                var current = $(this);
6583                linkAccount(current);
6584            });
6585        };
6586
6587        const linkAccount = function (current) {
6588            var $form = current.parents('form');
6589            var $messages = $('#hz-link-messages');
6590            const config = houzez.Core.config;
6591
6592            $.ajax({
6593                type: 'post',
6594                url: config.ajaxurl,
6595                dataType: 'json',
6596                data: $form.serialize(),
6597                beforeSend: function () {
6598                    current.find('.houzez-loader-js').addClass('loader-show');
6599                },
6600                complete: function () {
6601                    current
6602                        .find('.houzez-loader-js')
6603                        .removeClass('loader-show');
6604                },
6605                success: function (response) {
6606                    if (response.success) {
6607                        houzez.Core.util.showSuccess($messages, response.msg);
6608                        window.location.replace(response.redirect_to);
6609                    } else {
6610                        houzez.Core.util.showError($messages, response.msg);
6611                    }
6612
6613                    // Reset captcha (supports both reCaptcha and Turnstile)
6614                    houzez.Core.util.resetCaptcha($form);
6615                },
6616                error: function (xhr, status, error) {
6617                    console.error('Link account error: ' + error);
6618                    houzez.Core.util.showError(
6619                        $messages,
6620                        'Error processing account link'
6621                    );
6622                },
6623            });
6624        };
6625
6626        const initCreateAccount = function () {
6627            $('#houzez-create-account-btn').on('click', function (e) {
6628                e.preventDefault();
6629                var current = $(this);
6630                createAccount(current);
6631            });
6632        };
6633
6634        const createAccount = function (current) {
6635            var $form = current.parents('form');
6636            var $messages = $('#hz-create-messages');
6637            const config = houzez.Core.config;
6638
6639            $.ajax({
6640                type: 'post',
6641                url: config.ajaxurl,
6642                dataType: 'json',
6643                data: $form.serialize(),
6644                beforeSend: function () {
6645                    current.find('.houzez-loader-js').addClass('loader-show');
6646                },
6647                complete: function () {
6648                    current
6649                        .find('.houzez-loader-js')
6650                        .removeClass('loader-show');
6651                },
6652                success: function (response) {
6653                    if (response.success) {
6654                        houzez.Core.util.showSuccess($messages, response.msg);
6655                        window.location.replace(response.redirect_to);
6656                    } else {
6657                        houzez.Core.util.showError($messages, response.msg);
6658                    }
6659                },
6660                error: function (xhr, status, error) {
6661                    console.error('Create account error: ' + error);
6662                    houzez.Core.util.showError(
6663                        $messages,
6664                        'Error creating account'
6665                    );
6666                },
6667            });
6668        };
6669
6670        const initSocialLoginPanel = function () {
6671            const $mainStepWrap = $('.main-step-wrap');
6672            const $newAccountWrap = $('.new-account-wrap');
6673            const $linkAccountWrap = $('.link-account-wrap');
6674
6675            const toggleVisibility = function (hide1, hide2, show) {
6676                hide1.hide();
6677                hide2.hide();
6678                show.show();
6679            };
6680
6681            $(document).on('click', '.btn-link-account', function (event) {
6682                event.preventDefault();
6683                toggleVisibility(
6684                    $mainStepWrap,
6685                    $newAccountWrap,
6686                    $linkAccountWrap
6687                );
6688            });
6689
6690            $(document).on('click', '.btn-create-account', function (event) {
6691                event.preventDefault();
6692                toggleVisibility(
6693                    $mainStepWrap,
6694                    $linkAccountWrap,
6695                    $newAccountWrap
6696                );
6697            });
6698
6699            $(document).on('click', '.hz-fb-cancel', function (event) {
6700                event.preventDefault();
6701                toggleVisibility(
6702                    $linkAccountWrap,
6703                    $newAccountWrap,
6704                    $mainStepWrap
6705                );
6706            });
6707        };
6708
6709        const init = function () {
6710            initFacebookLogin();
6711            initGoogleLogin();
6712            initLinkAccount();
6713            initCreateAccount();
6714            initSocialLoginPanel();
6715        };
6716
6717        return {
6718            init: init,
6719            loginViaFacebook: loginViaFacebook,
6720            loginViaGoogle: loginViaGoogle,
6721            linkAccount: linkAccount,
6722            createAccount: createAccount,
6723        };
6724    })();
6725
6726    // Payments module
6727    houzez.Payments = (function () {
6728        /**
6729         * Handle changing listing fee for featured properties
6730         */
6731        const changeListingFeeForFeatured = function () {
6732            $('.prop_featured').on('change', function () {
6733                var currency_symbol = houzez_vars.currency_symbol;
6734                var currency_position = houzez_vars.currency_position;
6735                var total_price,
6736                    total_price_with_currency,
6737                    price_regular_with_currency,
6738                    price_regular_with_tax_currency;
6739
6740                var price_regular = parseFloat($('#submission_price').text());
6741                var price_featured = parseFloat(
6742                    $('#submission_featured_price').text()
6743                );
6744
6745                // Get tax information
6746                var submission_tax_percent =
6747                    parseFloat($('#submission_tax_percent').text()) || 0;
6748                var featured_tax_percent =
6749                    parseFloat($('#featured_tax_percent').text()) || 0;
6750                var submission_tax_amount =
6751                    parseFloat($('#submission_tax_amount').text()) || 0;
6752                var featured_tax_amount =
6753                    parseFloat($('#featured_tax_amount').text()) || 0;
6754
6755                // Calculate total with taxes
6756                var price_regular_with_tax =
6757                    price_regular + submission_tax_amount;
6758                var price_featured_with_tax =
6759                    price_featured + featured_tax_amount;
6760
6761                total_price = price_regular_with_tax + price_featured_with_tax;
6762
6763                if (currency_position === 'after') {
6764                    price_regular_with_currency =
6765                        price_regular + '' + currency_symbol;
6766                    price_regular_with_tax_currency =
6767                        price_regular_with_tax + '' + currency_symbol;
6768                    total_price_with_currency =
6769                        total_price + '' + currency_symbol;
6770                } else {
6771                    price_regular_with_currency =
6772                        currency_symbol + '' + price_regular;
6773                    price_regular_with_tax_currency =
6774                        currency_symbol + '' + price_regular_with_tax;
6775                    total_price_with_currency =
6776                        currency_symbol + '' + total_price;
6777                }
6778
6779                if ($(this).is(':checked')) {
6780                    $('#submission_total_price').text(
6781                        total_price_with_currency
6782                    );
6783                    $('#featured_pay').val(1);
6784                    $('#houzez_listing_price').val(total_price);
6785                    $('input[name="pay_ammout"]').val(total_price * 100);
6786                } else {
6787                    $('#submission_total_price').text(
6788                        price_regular_with_tax_currency
6789                    );
6790                    $('#featured_pay').val(0);
6791                    $('input[name="pay_ammout"]').val(
6792                        price_regular_with_tax * 100
6793                    );
6794                    $('#houzez_listing_price').val(price_regular_with_tax);
6795                }
6796                return false;
6797            });
6798        };
6799
6800        /**
6801         * Handle Paypal payment for per listing
6802         * @param {number} property_id - Property ID
6803         * @param {number} is_prop_featured - Is property featured
6804         * @param {number} is_prop_upgrade - Is property upgrade
6805         * @param {string} relist_mode - Relist mode
6806         */
6807        const paypalPerListingPayment = function (
6808            property_id,
6809            is_prop_featured,
6810            is_prop_upgrade,
6811            relist_mode
6812        ) {
6813            $.ajax({
6814                type: 'post',
6815                url: houzez.Core.config.ajaxurl,
6816                data: {
6817                    action: 'houzez_property_paypal_payment',
6818                    prop_id: property_id,
6819                    is_prop_featured: is_prop_featured,
6820                    is_prop_upgrade: is_prop_upgrade,
6821                    relist_mode: relist_mode,
6822                },
6823                success: function (response) {
6824                    window.location.href = response;
6825                },
6826                error: function (xhr, status, error) {
6827                    var err = eval('(' + xhr.responseText + ')');
6828                    console.log(err.Message);
6829                },
6830            });
6831        };
6832
6833        /**
6834         * Handle Stripe payment for per listing
6835         * @param {number} property_id - Property ID
6836         * @param {number} is_prop_featured - Is property featured
6837         * @param {number} is_prop_upgrade - Is property upgrade
6838         * @param {string} relist_mode - Relist mode
6839         */
6840        const stripePerListingPayment = function (
6841            property_id,
6842            is_prop_featured,
6843            is_prop_upgrade,
6844            relist_mode
6845        ) {
6846            $.ajax({
6847                type: 'post',
6848                url: houzez.Core.config.ajaxurl,
6849                dataType: 'JSON',
6850                data: {
6851                    action: 'houzez_property_stripe_payment',
6852                    prop_id: property_id,
6853                    is_prop_featured: is_prop_featured,
6854                    is_prop_upgrade: is_prop_upgrade,
6855                    relist_mode: relist_mode,
6856                },
6857                success: function (response) {
6858                    if (response.status) {
6859                        window.location.href = response.paymeny_link;
6860                    } else {
6861                        alert(response.message);
6862                    }
6863                },
6864                error: function (xhr, status, error) {
6865                    var err = eval('(' + xhr.responseText + ')');
6866                    console.log(err.Message);
6867                },
6868            });
6869        };
6870
6871        /**
6872         * Handle bank transfer per listing payment
6873         * @param {number} prop_id - Property ID
6874         * @param {number} listing_price - Listing price
6875         */
6876        const bankTransferPerListing = function (prop_id, listing_price) {
6877            var is_featured = $('input[name="featured_pay"]').val();
6878            var is_upgrade = $('input[name="is_upgrade"]').val();
6879
6880            jQuery.ajax({
6881                type: 'POST',
6882                url: houzez.Core.config.ajaxurl,
6883                data: {
6884                    action: 'houzez_direct_pay_per_listing',
6885                    prop_id: prop_id,
6886                    is_featured: is_featured,
6887                    is_upgrade: is_upgrade,
6888                },
6889                success: function (data) {
6890                    window.location.href = data;
6891                },
6892                error: function (errorThrown) {},
6893            });
6894        };
6895
6896        /**
6897         * Handle WooCommerce payment for per listing
6898         * @param {number} listID - Listing ID
6899         * @param {number} is_featured - Is property featured
6900         */
6901        const wooCommercePerListingPayment = function (listID, is_featured) {
6902            $.ajax({
6903                type: 'POST',
6904                url: houzez.Core.config.ajaxurl,
6905                data: {
6906                    action: 'houzez_perlist_woo_pay',
6907                    listing_id: listID,
6908                    is_featured: is_featured,
6909                },
6910                success: function (data) {
6911                    if (data.success != false) {
6912                        window.location.href = houzez_vars.woo_checkout_url;
6913                    } else {
6914                        houzez.Core.util.processingModalClose();
6915                    }
6916                },
6917                error: function (errorThrown) {
6918                    console.log(errorThrown);
6919                    houzez.Core.util.processingModalClose();
6920                },
6921            });
6922        };
6923
6924        /**
6925         * Handle WooCommerce payment for package
6926         * @param {number} packId - Package ID
6927         */
6928        const wooCommercePackagePayment = function (packId) {
6929            $.ajax({
6930                type: 'POST',
6931                url: houzez.Core.config.ajaxurl,
6932                data: {
6933                    action: 'houzez_woo_pay_package',
6934                    package_id: packId,
6935                },
6936                success: function (data) {
6937                    if (data.success != false) {
6938                        window.location.href = houzez_vars.woo_checkout_url;
6939                    } else {
6940                        houzez.Core.util.processingModalClose();
6941                    }
6942                },
6943                error: function (errorThrown) {
6944                    console.log(errorThrown);
6945                    houzez.Core.util.processingModalClose();
6946                },
6947            });
6948        };
6949
6950        /**
6951         * Initialize payment event listeners
6952         */
6953        const initPaymentEvents = function () {
6954            $('#houzez_complete_order').on('click', function (e) {
6955                e.preventDefault();
6956                var payment_gateway,
6957                    is_prop_featured,
6958                    is_prop_upgrade,
6959                    relist_mode;
6960                var property_id, houzez_listing_price;
6961
6962                payment_gateway = $(
6963                    "input[name='houzez_payment_type']:checked"
6964                ).val();
6965                is_prop_featured = $("input[name='featured_pay']").val();
6966                is_prop_upgrade = $("input[name='is_upgrade']").val();
6967                relist_mode = $("input[name='relist_mode']").val();
6968
6969                property_id = $('#houzez_property_id').val();
6970                houzez_listing_price = $('#houzez_listing_price').val();
6971
6972                if (payment_gateway == 'paypal') {
6973                    houzez.Core.util.processingModal(
6974                        houzez_vars.paypal_connecting
6975                    );
6976                    paypalPerListingPayment(
6977                        property_id,
6978                        is_prop_featured,
6979                        is_prop_upgrade,
6980                        relist_mode
6981                    );
6982                } else if (payment_gateway == 'stripe') {
6983                    houzez.Core.util.processingModal(
6984                        houzez.Core.config.processing_text
6985                    );
6986                    stripePerListingPayment(
6987                        property_id,
6988                        is_prop_featured,
6989                        is_prop_upgrade,
6990                        relist_mode
6991                    );
6992                } else if (payment_gateway == 'direct_pay') {
6993                    houzez.Core.util.processingModal(
6994                        houzez.Core.config.processing_text
6995                    );
6996                    bankTransferPerListing(property_id, houzez_listing_price);
6997                }
6998                return;
6999            });
7000
7001            // WooCommerce Per Listing Payment
7002            $('.houzez-woocommerce-pay').on('click', function (e) {
7003                e.preventDefault();
7004
7005                const listID = $(this).data('listid');
7006                const is_featured = $(this).data('featured');
7007
7008                houzez.Core.util.processingModal(
7009                    houzez.Core.config.processing_text
7010                );
7011                wooCommercePerListingPayment(listID, is_featured);
7012            });
7013
7014            // WooCommerce Package Payment
7015            $('.houzez-woocommerce-package').on('click', function (e) {
7016                e.preventDefault();
7017
7018                if (
7019                    parseInt(houzez.Core.config.userID, 10) === 0 ||
7020                    houzez.Core.config.userID === undefined
7021                ) {
7022                    $('#login-register-form').modal('show');
7023                    $('.login-form-tab').addClass('active show');
7024                    $('.modal-toggle-1.nav-link').addClass('active');
7025                } else {
7026                    const packId = $(this).data('packid');
7027
7028                    houzez.Core.util.processingModal(
7029                        houzez.Core.config.processing_text
7030                    );
7031                    wooCommercePackagePayment(packId);
7032                }
7033            });
7034        };
7035
7036        /**
7037         * Initialize payment method options and UI
7038         */
7039        const initPaymentOptions = function () {
7040            // Get payment method selector
7041            const $paymentMethodSelect = $('.payment-method-select');
7042
7043            if ($paymentMethodSelect.length === 0) {
7044                return; // Exit if element doesn't exist on the page
7045            }
7046
7047            // Initialize UI based on current payment type
7048            const initPaymentUI = function (paymentType) {
7049                // Hide all payment sections first
7050                $(
7051                    '.recurring-payment-paypal, .recurring-payment-stripe'
7052                ).hide();
7053
7054                // Show appropriate section based on payment type
7055                if (paymentType === 'paypal') {
7056                    $('.recurring-payment-stripe').hide();
7057                    $('.recurring-payment-paypal').fadeIn(300);
7058                } else if (paymentType === 'stripe') {
7059                    $('.recurring-payment-paypal').hide();
7060                    $('.recurring-payment-stripe').fadeIn(300);
7061                }
7062            };
7063
7064            // Set initial state based on default selected payment method
7065            let initialPaymentType = $paymentMethodSelect.val();
7066            initPaymentUI(initialPaymentType);
7067
7068            // Handle payment method change event
7069            $paymentMethodSelect.on('change', function () {
7070                const paymentType = $(this).val();
7071                initPaymentUI(paymentType);
7072            });
7073
7074            // Handle recurring payment checkbox for Stripe
7075            $('#stripe_package_recurring').on('change', function () {
7076                if ($(this).is(':checked')) {
7077                    if ($('#houzez_stripe_recurring').length === 0) {
7078                        $('.houzez_payment_form').append(
7079                            '<input type="hidden" name="houzez_stripe_recurring" id="houzez_stripe_recurring" value="1">'
7080                        );
7081                    }
7082                } else {
7083                    $('#houzez_stripe_recurring').remove();
7084                }
7085            });
7086
7087            // Initialize recurring checkbox to match its current state
7088            if (
7089                $('#stripe_package_recurring').is(':checked') &&
7090                $('#houzez_stripe_recurring').length === 0
7091            ) {
7092                $('.houzez_payment_form').append(
7093                    '<input type="hidden" name="houzez_stripe_recurring" id="houzez_stripe_recurring" value="1">'
7094                );
7095            }
7096        };
7097
7098        /**
7099         * Initialize membership payment functionality
7100         */
7101        const initMembershipPayment = function () {
7102            /**
7103             * Handle Stripe package payment
7104             * @param {number} houzez_package_id - Package ID
7105             * @param {boolean} is_stripe_recurring - Whether the payment is recurring
7106             */
7107            const stripePackagePayment = function (
7108                houzez_package_id,
7109                is_stripe_recurring
7110            ) {
7111                $.ajax({
7112                    type: 'POST',
7113                    url: houzez.Core.config.ajaxurl,
7114                    dataType: 'JSON',
7115                    data: {
7116                        action: 'houzez_stripe_package_payment',
7117                        package_id: houzez_package_id,
7118                        is_stripe_recurring: is_stripe_recurring,
7119                    },
7120                    success: function (response) {
7121                        if (response.status) {
7122                            window.location.href = response.paymeny_link;
7123                        } else {
7124                            alert(response.message);
7125                        }
7126                    },
7127                    error: function (xhr, status, error) {
7128                        var err = eval('(' + xhr.responseText + ')');
7129                        console.log(err.Message);
7130                    },
7131                });
7132            };
7133
7134            /**
7135             * Handle PayPal package payment
7136             * @param {number} houzez_package_price - Package price
7137             * @param {string} houzez_package_name - Package name
7138             * @param {number} houzez_package_id - Package ID
7139             */
7140            const paypalPackagePayment = function (
7141                houzez_package_price,
7142                houzez_package_name,
7143                houzez_package_id
7144            ) {
7145                $.ajax({
7146                    type: 'POST',
7147                    url: houzez.Core.config.ajaxurl,
7148                    data: {
7149                        action: 'houzez_paypal_package_payment',
7150                        houzez_package_price: houzez_package_price,
7151                        houzez_package_name: houzez_package_name,
7152                        houzez_package_id: houzez_package_id,
7153                    },
7154                    success: function (data) {
7155                        window.location.href = data;
7156                    },
7157                    error: function (xhr, status, error) {
7158                        var err = eval('(' + xhr.responseText + ')');
7159                        console.log(err.Message);
7160                    },
7161                });
7162            };
7163
7164            /**
7165             * Handle recurring PayPal package payment
7166             * @param {number} houzez_package_price - Package price
7167             * @param {string} houzez_package_name - Package name
7168             * @param {number} houzez_package_id - Package ID
7169             */
7170            const recuringPaypalPackagePayment = function (
7171                houzez_package_price,
7172                houzez_package_name,
7173                houzez_package_id
7174            ) {
7175                jQuery.ajax({
7176                    type: 'POST',
7177                    url: houzez.Core.config.ajaxurl,
7178                    data: {
7179                        action: 'houzez_recuring_paypal_package_payment',
7180                        houzez_package_name: houzez_package_name,
7181                        houzez_package_id: houzez_package_id,
7182                        houzez_package_price: houzez_package_price,
7183                    },
7184                    success: function (data) {
7185                        window.location.href = data;
7186                    },
7187                    error: function (xhr, status, error) {
7188                        var err = eval('(' + xhr.responseText + ')');
7189                        console.log(err.Message);
7190                    },
7191                });
7192            };
7193
7194            /**
7195             * Handle direct bank transfer package payment
7196             * @param {number} houzez_package_id - Package ID
7197             * @param {number} houzez_package_price - Package price
7198             * @param {string} houzez_package_name - Package name
7199             */
7200            const directBankTransferPackage = function (
7201                houzez_package_id,
7202                houzez_package_price,
7203                houzez_package_name
7204            ) {
7205                jQuery.ajax({
7206                    type: 'POST',
7207                    url: houzez.Core.config.ajaxurl,
7208                    data: {
7209                        action: 'houzez_direct_pay_package',
7210                        selected_package: houzez_package_id,
7211                    },
7212                    success: function (data) {
7213                        window.location.href = data;
7214                    },
7215                    error: function (errorThrown) {},
7216                });
7217            };
7218
7219            /**
7220             * Handle free membership package
7221             * @param {number} houzez_package_id - Package ID
7222             */
7223            const freeMembershipPackage = function (houzez_package_id) {
7224                jQuery.ajax({
7225                    type: 'POST',
7226                    url: houzez.Core.config.ajaxurl,
7227                    data: {
7228                        action: 'houzez_free_membership_package',
7229                        selected_package: houzez_package_id,
7230                    },
7231                    success: function (data) {
7232                        window.location.href = data;
7233                    },
7234                    error: function (errorThrown) {},
7235                });
7236            };
7237
7238            /**
7239             * Process membership data based on payment method
7240             * @param {jQuery} currnt - Current jQuery element
7241             * @returns {boolean} - Always returns false
7242             */
7243            const membershipData = function (currnt) {
7244                var payment_gateway = $(
7245                    "input[name='houzez_payment_type']:checked"
7246                ).val();
7247                var houzez_package_price = $(
7248                    "input[name='houzez_package_price']"
7249                ).val();
7250                var houzez_package_id = $(
7251                    "input[name='houzez_package_id']"
7252                ).val();
7253                var houzez_package_name = $('#houzez_package_name').text();
7254
7255                if (payment_gateway == 'paypal') {
7256                    houzez.Core.util.processingModal(
7257                        houzez_vars.paypal_connecting
7258                    );
7259                    if ($('#paypal_package_recurring').is(':checked')) {
7260                        recuringPaypalPackagePayment(
7261                            houzez_package_price,
7262                            houzez_package_name,
7263                            houzez_package_id
7264                        );
7265                    } else {
7266                        paypalPackagePayment(
7267                            houzez_package_price,
7268                            houzez_package_name,
7269                            houzez_package_id
7270                        );
7271                    }
7272                } else if (payment_gateway == 'stripe') {
7273                    houzez.Core.util.processingModal(
7274                        houzez.Core.config.processing_text
7275                    );
7276                    var is_stripe_recurring = $('#houzez_stripe_recurring').is(
7277                        ':checked'
7278                    );
7279                    stripePackagePayment(
7280                        houzez_package_id,
7281                        is_stripe_recurring
7282                    );
7283                } else if (payment_gateway == 'direct_pay') {
7284                    houzez.Core.util.processingModal(
7285                        houzez.Core.config.processing_text
7286                    );
7287                    directBankTransferPackage(
7288                        houzez_package_id,
7289                        houzez_package_price,
7290                        houzez_package_name
7291                    );
7292                } else {
7293                    houzez.Core.util.processingModal(
7294                        houzez.Core.config.processing_text
7295                    );
7296                    freeMembershipPackage(houzez_package_id);
7297                }
7298
7299                return false;
7300            };
7301
7302            /**
7303             * Register user with membership
7304             * @param {jQuery} currnt - Current jQuery element
7305             */
7306            const registerUserWithMembership = function (currnt) {
7307                var $form = currnt.parents('form');
7308                var $messages = $('#packmem-msgs');
7309
7310                $.ajax({
7311                    type: 'post',
7312                    url: houzez.Core.config.ajaxurl,
7313                    dataType: 'json',
7314                    data: $form.serialize(),
7315                    beforeSend: function () {
7316                        currnt
7317                            .find('.houzez-loader-js')
7318                            .addClass('loader-show');
7319                    },
7320                    complete: function () {
7321                        currnt
7322                            .find('.houzez-loader-js')
7323                            .removeClass('loader-show');
7324                    },
7325                    success: function (response) {
7326                        if (response.success) {
7327                            membershipData(currnt);
7328                        } else {
7329                            $('html, body').animate(
7330                                {
7331                                    scrollTop: $(
7332                                        '.frontend-submission-page'
7333                                    ).offset().top,
7334                                },
7335                                'slow'
7336                            );
7337                            houzez.Core.util.showError($messages, response.msg);
7338                        }
7339                    },
7340                    error: function (xhr, status, error) {
7341                        var err = eval('(' + xhr.responseText + ')');
7342                        console.log(err.Message);
7343                        houzez.Core.util.showError(
7344                            $messages,
7345                            err.Message || 'An error occurred'
7346                        );
7347                    },
7348                });
7349            };
7350
7351            // Handle membership payment submission
7352            $('#houzez_complete_membership').on('click', function (e) {
7353                e.preventDefault();
7354                var currnt = $(this);
7355                if (
7356                    parseInt(houzez.Core.config.userID, 10) === 0 ||
7357                    houzez.Core.config.userID === undefined
7358                ) {
7359                    registerUserWithMembership(currnt);
7360                    return;
7361                }
7362                membershipData(currnt);
7363            });
7364
7365            // Expose functions for external use
7366            return {
7367                stripePackagePayment: stripePackagePayment,
7368                paypalPackagePayment: paypalPackagePayment,
7369                recuringPaypalPackagePayment: recuringPaypalPackagePayment,
7370                directBankTransferPackage: directBankTransferPackage,
7371                freeMembershipPackage: freeMembershipPackage,
7372                membershipData: membershipData,
7373                registerUserWithMembership: registerUserWithMembership,
7374            };
7375        };
7376
7377        const init = function () {
7378            changeListingFeeForFeatured();
7379            initPaymentEvents();
7380            initPaymentOptions();
7381
7382            // Initialize membership payment
7383            const membershipFunctions = initMembershipPayment();
7384
7385            // Expose membership functions to the module
7386            Object.assign(this, membershipFunctions);
7387        };
7388
7389        return {
7390            init: init,
7391            changeListingFeeForFeatured: changeListingFeeForFeatured,
7392            paypalPerListingPayment: paypalPerListingPayment,
7393            stripePerListingPayment: stripePerListingPayment,
7394            bankTransferPerListing: bankTransferPerListing,
7395            wooCommercePerListingPayment: wooCommercePerListingPayment,
7396            wooCommercePackagePayment: wooCommercePackagePayment,
7397            initPaymentOptions: initPaymentOptions,
7398            initMembershipPayment: initMembershipPayment,
7399        };
7400    })();
7401
7402    houzez.DirectMessage = (function () {
7403        /**
7404         * Initialize direct message functionality
7405         */
7406        const initDirectMessage = function () {
7407            $('.msg-login-required').on('click', function () {
7408                $('.modal-toggle-1').addClass('active');
7409                jQuery('.login-form-tab').addClass('active show');
7410            });
7411
7412            $('.houzez-send-message').on('click', function (e) {
7413                e.preventDefault();
7414
7415                var $result;
7416                var $this = $(this);
7417                var $form = $this.parents('form');
7418                var $form_wrap = $this.parents('.property-form-wrap');
7419                $result = $form_wrap.find('.form_messages');
7420                var $is_bottom = $('.is_bottom').val();
7421                if ($is_bottom == 'bottom') {
7422                    $result = $form.find('.form_messages');
7423                }
7424                $result.empty();
7425
7426                var property_id = $('input[name="listing_id"]').val();
7427                var message = $form.find('.hz-form-message').val();
7428                var security = $(
7429                    'input[name="property_agent_contact_security"]'
7430                ).val();
7431
7432                $.ajax({
7433                    url: houzez.Core.config.ajaxurl,
7434                    data: {
7435                        action: 'houzez_start_thread',
7436                        property_id: property_id,
7437                        message: message,
7438                        start_thread_form_ajax: security,
7439                    },
7440                    method: $form.attr('method'),
7441                    dataType: 'JSON',
7442
7443                    beforeSend: function () {
7444                        $this.find('.houzez-loader-js').addClass('loader-show');
7445                    },
7446                    success: function (response) {
7447                        if (response.success) {
7448                            $form
7449                                .find(
7450                                    'input[name="name"], input[name="mobile"], input[name="email"]'
7451                                )
7452                                .val('');
7453                            $form.find('textarea').val('');
7454                            if ($is_bottom == 'bottom') {
7455                                houzez.Core.util.showSuccess(
7456                                    $result,
7457                                    response.msg
7458                                );
7459                            } else {
7460                                $result
7461                                    .empty()
7462                                    .append(
7463                                        '<p class="success text-success"><i class="fa fa-check"></i> ' +
7464                                            response.msg +
7465                                            '</p>'
7466                                    );
7467                            }
7468                        } else {
7469                            if ($is_bottom == 'bottom') {
7470                                houzez.Core.util.showError(
7471                                    $result,
7472                                    response.msg
7473                                );
7474                            } else {
7475                                $result
7476                                    .empty()
7477                                    .append(
7478                                        '<p class="error text-danger"><i class="fas fa-times"></i> ' +
7479                                            response.msg +
7480                                            '</p>'
7481                                    );
7482                            }
7483                        }
7484
7485                        $this
7486                            .find('.houzez-loader-js')
7487                            .removeClass('loader-show');
7488
7489                        // Reset captcha (supports both reCaptcha and Turnstile)
7490                        houzez.Core.util.resetCaptcha($form);
7491
7492                        if (houzez_vars.agent_redirection != '') {
7493                            setTimeout(function () {
7494                                window.location.replace(
7495                                    houzez_vars.agent_redirection
7496                                );
7497                            }, 500);
7498                        }
7499                    },
7500                    error: function (xhr, status, error) {
7501                        var err = eval('(' + xhr.responseText + ')');
7502                        console.log(err.Message);
7503                    },
7504                    complete: function () {
7505                        $this
7506                            .find('.houzez-loader-js')
7507                            .removeClass('loader-show');
7508                    },
7509                });
7510            });
7511        };
7512
7513        const init = function () {
7514            // Only initialize if we're on a property page and the message form exists
7515            if (
7516                houzez.Core.config.is_singular_property &&
7517                $('.houzez-send-message').length > 0
7518            ) {
7519                initDirectMessage();
7520            }
7521        };
7522
7523        return {
7524            init: init,
7525            initDirectMessage: initDirectMessage,
7526        };
7527    })();
7528    // For backward compatibility
7529    window.houzezGetCookie = houzez.Core.util.getCookie;
7530    window.houzezSetCookie = houzez.Core.util.setCookie;
7531    // window.houzezAddCommas = houzez.Core.util.addCommas;
7532    // window.houzezFormatNumber = houzez.Core.util.formatNumber;
7533    // window.houzezGetLoaderHtml = houzez.Core.util.getLoaderHtml;
7534    // window.houzezProcessingModal = houzez.Core.util.processingModal;
7535    // window.houzezProcessingModalClose = houzez.Core.util.processingModalClose;
7536    // window.houzezGetSuccessHtml = houzez.Core.util.getSuccessHtml;
7537    // window.houzezGetErrorHtml = houzez.Core.util.getErrorHtml;
7538    // window.houzezShowSuccess = houzez.Core.util.showSuccess;
7539    // window.houzezShowError = houzez.Core.util.showError;
7540    window.HouzezDebounce = houzez.Core.util.debounce;
7541    // window.parseBool = houzez.Core.util.parseBool;
7542    // window.number_format = houzez.Core.util.numberFormat;
7543    // window.thousandSeparator = houzez.Core.util.thousandSeparator;
7544    // window.currencyFormate = houzez.Core.util.currencyFormat;
7545
7546    // Login module backward compatibility
7547    window.houzez_process_login = houzez.Login.processLogin;
7548    window.houzez_process_register = houzez.Login.processRegister;
7549    window.houzez_process_reset = houzez.Login.processForgotPassword;
7550    window.houzez_social_login_panel = houzez.Login.socialLoginPanel;
7551
7552    // Favorites module backward compatibility
7553    window.houzez_init_add_favorite = houzez.Favorites.initAddFavorite;
7554    window.houzez_init_remove_favorite = houzez.Favorites.initRemoveFavorite;
7555    window.add_to_favorite = houzez.Favorites.add_to_favorite;
7556    window.remove_from_favorite = houzez.Favorites.remove_from_favorite;
7557    window.houzez_check_favourites = houzez.Favorites.check_favorites;
7558
7559    // Compare module backward compatibility
7560    window.add_to_compare = houzez.Compare.add_to_compare;
7561    window.remove_from_compare = houzez.Compare.remove_from_compare;
7562    window.compare_for_ajax = houzez.Compare.compare_for_ajax;
7563    window.compare_for_ajax_map = houzez.Compare.compare_for_ajax;
7564
7565    // Sliders module backward compatibility
7566    // window.propertyDetailGallery = houzez.Sliders.propertyDetailGallery;
7567    // window.propertyBannerSlider = houzez.Sliders.propertyBannerSlider;
7568    // window.lightboxSlider = houzez.Sliders.lightboxSlider;
7569    // window.variableWidthSlider = houzez.Sliders.variableWidthSlider;
7570    // window.testimonialsSliderV1 = houzez.Sliders.testimonialsSliderV1;
7571    // window.agentsCarousel = houzez.Sliders.agentsCarousel;
7572    // window.partnersCarousel = houzez.Sliders.partnersCarousel;
7573
7574    // Mortgage module backward compatibility
7575    window.calculateMonthlyPayment = houzez.Core.util.calculateMonthlyPayment;
7576    window.mortgage_calucaltion_section = houzez.Mortgage.mortgageCalculation;
7577    window.updateChart = houzez.Mortgage.updateChart;
7578
7579    // Property preview backward compatibility
7580    window.houzez_listing_lightbox = function () {
7581        houzez.PropertyPreview.init();
7582    };
7583
7584    // Add backward compatibility for grid gallery
7585    window.houzez_grid_image_gallery = function () {
7586        houzez.Sliders.gridImageGallery();
7587    };
7588
7589    window.houzez_grid_call_to_action = function () {
7590        houzez.Core.gridCallToAction();
7591    };
7592
7593    /**
7594     * Initialize all modules when DOM is ready
7595     */
7596    $(document).ready(function () {
7597        // Initialize core first
7598        houzez.Core.init();
7599        houzez.Logo.init();
7600        houzez.ElementorMobileMenu.init();
7601        houzez.MobileNav.init();
7602        houzez.TopBannerFullScreen.init();
7603        houzez.Sticky.init();
7604        houzez.Search.init();
7605        houzez.PropertyPreview.init();
7606        houzez.LocationFilter.init();
7607
7608        // Then initialize other modules
7609        houzez.Blog.init();
7610        houzez.Login.init();
7611        houzez.Favorites.init();
7612        houzez.Compare.init();
7613        //houzez.Properties.init();
7614        houzez.Sliders.init();
7615        houzez.Pagination.init();
7616        houzez.Mortgage.init();
7617        houzez.ContactForms.init();
7618        houzez.PropertiesTabs.init();
7619        houzez.SingleProperty.init();
7620        houzez.RealtorStats.init();
7621        houzez.CurrencySwitcher.init();
7622        houzez.AreaSwitcher.init();
7623        houzez.Payments.init();
7624        houzez.DirectMessage.init();
7625
7626        if (
7627            $('.hz-facebook-login').length > 0 ||
7628            $('.hz-google-login').length > 0 ||
7629            $('.hz-fb-main-wrap').length > 0
7630        ) {
7631            houzez.SocialLogin.init();
7632        }
7633
7634        // Initialize Reviews module only if the submit button exists
7635        if ($('#submit-review').length > 0) {
7636            houzez.Reviews.init();
7637        }
7638        if ($('.houzez-parallax').length > 0) {
7639            houzez.Parallax.init();
7640        }
7641    });
7642
7643    // Make houzez object available globally
7644    window.houzez = houzez;
7645})(jQuery);

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.