PageSourceSearch

https://followthecamino.com/wp-content/themes/Impreza-child/js/custom.js?ver=1790331411

js followthecamino.com collected 2026-10-02 05:23:03 UTC 27,484 bytes, 878 lines download raw bytes

1jQuery(document).ready(function ($) {
2	
3	
4    function updateButtonState(button) {
5        var postID = button.data('post-id');
6        var userID = button.data('user-id');
7        var wishlistStatus;
8
9        if (userID) {
10			$.ajax({
11				type: 'POST',
12				url: ajaxurl,
13				data: {
14					action: 'get_wishlist_status',
15					post_id: postID,
16					user_id: userID,
17				},
18				success: function (response) {
19					wishlistStatus = response;
20	
21					if (wishlistStatus === 'in_wishlist') {
22						button.html('<i class="fas fa-regular fa-heart"></i>');
23						button.removeClass('heart-icon-empty').addClass('heart-icon');
24						
25					} else if (wishlistStatus === 'not_in_wishlist') {
26						button.html('<i class="fal fa-regular fa-heart"></i>');
27						button.removeClass('heart-icon').addClass('heart-icon-empty');
28						
29						if (window.location.pathname === '/en/my-wishlist/' || window.location.pathname === '/es/my-wishlist/' ) {
30							removeWishlistDataFromMainPage(postID);
31						}
32						
33					}
34					fetchWishlistData();
35				},
36			});
37		}
38		else
39		{
40			// Guest user - add the item to a cookie
41			$.ajax({
42				type: 'POST',
43				url: ajaxurl,
44				data: {
45					action: 'get_wishlist_status',
46					post_id: postID,
47				},
48				success: function (response) {
49					wishlistStatus = response;
50	
51					if (wishlistStatus === 'added') {
52						button.html('<i class="fas fa-regular fa-heart"></i>');
53						button.removeClass('heart-icon-empty').addClass('heart-icon');
54						
55					} else if (wishlistStatus === 'removed') {
56						button.html('<i class="fal fa-regular fa-heart"></i>');
57						button.removeClass('heart-icon').addClass('heart-icon-empty');
58						if (window.location.pathname === '/en/my-wishlist/' || window.location.pathname === '/es/my-wishlist/' ) {
59							removeWishlistDataFromMainPage(postID);
60						}
61						
62					}
63					fetchWishlistData();
64				},
65			});		
66		}	
67		
68    }
69
70    jQuery(document).on('click', '.wishlist-button', function(e) {
71		e.preventDefault();
72
73		var button = jQuery(this);
74		var postID = button.data('post-id');
75		var userID = button.data('user-id');
76		
77		// Prevent double-clicks while AJAX is running
78		if (button.hasClass('loading')) {
79			return;
80		}
81		button.addClass('loading');
82		button.prop('disabled', true); // disable button
83		button.css('opacity', '0.5'); // optional visual feedback
84
85		jQuery.ajax({
86			url: ajaxurl, // make sure ajax_object is localized in PHP
87			type: 'POST',
88			data: {
89				action: userID > 0 ? 'update_wishlist' : 'update_guest_wishlist',
90				post_id: postID,
91				user_id: userID
92			},
93			success: function(response) {
94				var wishlistStatus = response.trim(); // remove any extra spaces
95
96				if (wishlistStatus === 'added') {
97					button.html('<i class="fas fa-regular fa-heart"></i>');
98					button.removeClass('heart-icon-empty').addClass('heart-icon');
99					fetchWishlistData();
100				} else if (wishlistStatus === 'removed') {
101					button.html('<i class="fal fa-regular fa-heart"></i>');
102					button.removeClass('heart-icon').addClass('heart-icon-empty');
103					fetchWishlistData();
104
105					if (window.location.pathname === '/en/my-wishlist/' || window.location.pathname === '/es/my-wishlist/' ) {
106						removeWishlistDataFromMainPage(postID);
107					}
108				} else {
109					console.log('Unexpected response:', wishlistStatus);
110				}
111				console.log('AJAX Response:', response);
112			},
113			complete: function() {
114            // Re-enable button after AJAX finishes
115            button.removeClass('loading');
116            button.prop('disabled', false);
117            button.css('opacity', '1');
118			}
119		});
120	});
121
122
123    // Call the function to set the initial button state on page load
124    updateButtonState($('.wishlist-button'));
125	
126	var wishlistCount = parseInt($('.custom_wishlist_count').text());
127	
128    // Append the count after the heart icon
129    var heartIcon = $('.wp-bottom-menu .fa-heart');
130    var countElement = $('<span class="wishlist-count-footer custom_wishlist_count">' + wishlistCount + '</span>');
131    heartIcon.after(countElement);
132	
133	function fetchWishlistData() {
134        $.ajax({
135            type: 'POST',
136            url: ajaxurl,
137            data: {
138                action: 'get_wishlist_data_count',
139            },
140            success: function (data) {
141                // Update the wishlist section with the new data
142				// parse the JSON string to an array
143				let parsedArray = JSON.parse(data);
144				// count the number of elements
145				let count = parsedArray.length;
146				
147                $('.custom_wishlist_count').html(count);
148				$('.custom_wishlist_count1').html(count);
149				localStorage.setItem('guest_wishlist_full_name', JSON.stringify(parsedArray));
150            },
151            error: function (error) {
152                console.log("Error fetching wishlist data:", error);
153            }
154        });
155    }
156	function removeWishlistDataFromMainPage(postID) {
157		
158		const article1 = document.querySelector(`article[data-id="${postID}"]`);
159		if (article1) {
160			article1.remove();
161		}
162		const article2 = document.querySelector(`.custom_tour_carousel_mobile article[data-id="${postID}"]`);
163		if (article2) {
164			article2.remove();
165		}
166	}
167	
168	
169	$('.gallery-slider').slick({
170        slidesToShow: 1,
171        slidesToScroll: 1,
172        arrows: false,
173        dots: false,
174		infinite: true,
175		autoplay:true,
176        speed: 300,
177        adaptiveHeight: true
178    });
179});
180jQuery(document).ready(function($) {
181    $('.month-grid-container').hover(function() {
182        $(this).find('.month-grid-tooltip').fadeIn();
183    }, function() {
184        $(this).find('.month-grid-tooltip').fadeOut();
185    });
186	
187	
188	
189	
190	$("#hb-slider").owlCarousel({
191      navigation : false, // Show next and prev buttons
192      dots:false,
193      slideSpeed : 300,
194      paginationSpeed : 400,
195	  autoplay:true,
196	  autoplayTimeout:7500,
197      smartSpeed:1000,
198	  loop:true,
199      items : 1, 
200      itemsDesktop : false,
201      itemsDesktopSmall : false,
202      itemsTablet: false,
203      itemsMobile : false
204 
205	});
206	
207	$("#close-header-banner").click(function(){
208		$(".header-banner-slider").hide();
209	})
210}); 
211
212
213
214
215/*Tanya Ticket Changes*/
216jQuery(document).ready(function($) {
217	
218	$('#tabbing-products .w-grid-item').each(function() {
219        var $item = $(this);
220        
221        // Find closest parent with a type-* class
222        var $parent = $item.closest('.type-post, .type-route, .type-camino-tours');
223
224        if ($parent.hasClass('type-post')) {
225            $item.append('<div class="custom-type-label">Blog Post</div>');
226        } else if ($parent.hasClass('type-route')) {
227            $item.append('<div class="custom-type-label">Collection</div>');
228        } else if ($parent.hasClass('type-camino-tours')) {
229            $item.append('<div class="custom-type-label">Product</div>');
230        }
231		
232    });
233	
234	$('.btn-view-more').on('click', function () {
235    const $btn = $(this);
236    const isExpanded = $btn.hasClass('btn-view-less');
237
238    if (isExpanded) {
239        $('.hide-service-mobile').css('display', 'none');
240        $btn.removeClass('btn-view-less').text('View More');
241    } else {
242        $('.hide-service-mobile').css('display', 'flex');
243        $btn.addClass('btn-view-less').text('View Less');
244    }
245	});
246
247	// Toggle route grid items
248	$('.btn-view-more-route').on('click', function () {
249		const $btn = $(this);
250		const isExpanded = $btn.hasClass('btn-view-less-route');
251
252		if (isExpanded) {
253			// Hide items from 4th onward
254			$('.custom_route_vertical_grid .w-grid-item:nth-child(n+4)').hide();
255			$btn.removeClass('btn-view-less-route').text('View More');
256		} else {
257			// Show items using block or flex
258			$('.custom_route_vertical_grid .w-grid-item:nth-child(n+4)').css('display', 'block');
259			$btn.addClass('btn-view-less-route').text('View Less');
260		}
261	});
262	
263	
264		  $('.mb-condense-text').each(function () {
265				const $container = $(this);
266				const $paragraphs = $container.find('p');
267
268				if ($paragraphs.length === 0) return;
269
270				const firstP = $paragraphs.eq(0);
271				const fullText = firstP.text().trim();
272				const fullHTML = firstP.html();
273				const dotIndex = fullText.indexOf('.');
274
275				if (dotIndex === -1) return; // no full stop
276
277				// Split content at the first dot
278				const visiblePart = fullText.slice(0, dotIndex + 1); // with dot
279				const hiddenPart = fullText.slice(dotIndex + 1).trim();
280
281				// HTML
282				const visibleHTML = `<p>${visiblePart}</p>`;
283				let hiddenHTML = '';
284
285				if (hiddenPart.length > 0) {
286				  hiddenHTML += `<p>${hiddenPart}</p>`;
287				}
288
289				// Add remaining paragraphs to hidden
290				$paragraphs.slice(1).each(function () {
291				  hiddenHTML += $(this).prop('outerHTML');
292				});
293
294				// Final structure
295				const readMoreBtn = `<button class="read-more-btn" aria-expanded="false" aria-label="Read More">Read More</button>`;
296				const readLessBtn = `<button class="read-less-btn" aria-expanded="true" aria-label="Read Less">Read Less</button>`;
297
298				$container.html(`${visibleHTML}${readMoreBtn}<div class="hidden-paragraphs" style="display:none;">${hiddenHTML}${readLessBtn}</div>`);
299			  });
300
301			  // Expand
302			  $(document).on('click', '.read-more-btn', function () {
303				const $container = $(this).closest('.mb-condense-text');
304				$container.find('.hidden-paragraphs').slideDown();
305				$(this).hide();
306			  });
307
308			  // Collapse
309			  $(document).on('click', '.read-less-btn', function () {
310				const $container = $(this).closest('.mb-condense-text');
311				$container.find('.hidden-paragraphs').slideUp();
312				$container.find('.read-more-btn').show();
313			  });
314
315	  
316	  //Highlight Section Content
317	    $('.custom_highlight_section.custom_highlight_section_mb .wpb_text_column.us_custom_679d515e .wpb_wrapper').each(function () {
318			const $container = $(this);
319			const $paragraphs = $container.find('p');
320
321			if ($paragraphs.length === 0) return;
322
323			const firstP = $paragraphs.eq(0);
324			const fullText = firstP.text().trim();
325			const fullHTML = firstP.html();
326			const dotIndex = fullText.indexOf('.');
327
328			if (dotIndex === -1) return; // no full stop found
329
330			const visiblePart = fullText.slice(0, dotIndex + 1); // up to and including the first period
331			const hiddenPart = fullText.slice(dotIndex + 1).trim();
332
333			let visibleHTML = `<p>${visiblePart}</p>`;
334			let hiddenHTML = '';
335
336			if (hiddenPart.length > 0) {
337			  hiddenHTML += `<p>${hiddenPart}</p>`;
338			}
339
340			$paragraphs.slice(1).each(function () {
341			  hiddenHTML += $(this).prop('outerHTML');
342			});
343
344			const readMoreBtn = `<button class="read-more-btn" aria-expanded="false" aria-label="Read More">Read More</button>`;
345			const readLessBtn = `<button class="read-less-btn" aria-expanded="true" aria-label="Read Less">Read Less</button>`;
346
347			// Set final HTML
348			$container.html(`${visibleHTML}${readMoreBtn}<div class="hidden-paragraphs" style="display:none;">${hiddenHTML}${readLessBtn}</div>`);
349		  });
350
351		  // Show hidden content
352		  $(document).on('click', '.read-more-btn', function () {
353			const $container = $(this).closest('.wpb_wrapper');
354			$container.find('.hidden-paragraphs').slideDown();
355			$(this).hide();
356		  });
357
358		  // Hide hidden content
359		  $(document).on('click', '.read-less-btn', function () {
360			const $container = $(this).closest('.wpb_wrapper');
361			$container.find('.hidden-paragraphs').slideUp();
362			$container.find('.read-more-btn').show();
363		  });
364	  
365	  
366	  //Itinerary Section Content
367		  
368		  
369		 $('.custom_itinerary_layout .mb-itinerary .custom_iti_days_de
369sc .wpb_text_column .wpb_wrapper').each(function () {
370				const $container = $(this);
371				const $paragraphs = $container.find('p');
372
373				if ($paragraphs.length === 0) return;
374
375				const firstP = $paragraphs.eq(0);
376				const fullText = firstP.text().trim();
377				const fullHTML = firstP.html();
378				const dotIndex = fullText.indexOf('.');
379
380				if (dotIndex === -1) return; // no full stop, don't change
381
382				const visiblePart = fullText.slice(0, dotIndex + 1); // includes the dot
383				const hiddenPart = fullText.slice(dotIndex + 1).trim();
384
385				let visibleHTML = `<p>${visiblePart}</p>`;
386				let hiddenHTML = '';
387
388				if (hiddenPart.length > 0) {
389				  hiddenHTML += `<p>${hiddenPart}</p>`;
390				}
391
392				// Add the rest of the <p> tags to hidden content
393				$paragraphs.slice(1).each(function () {
394				  hiddenHTML += $(this).prop('outerHTML');
395				});
396
397				const readMoreBtn = `<button class="read-more-btn" aria-expanded="false" aria-label="Read More">Read More</button>`;
398				const readLessBtn = `<button class="read-less-btn" aria-expanded="true" aria-label="Read Less">Read Less</button>`;
399
400				$container.html(`${visibleHTML}${readMoreBtn}<div class="hidden-paragraphs" style="display:none;">${hiddenHTML}${readLessBtn}</div>`);
401			  });
402
403			  // Expand hidden content
404			  $(document).on('click', '.read-more-btn', function () {
405				const $container = $(this).closest('.wpb_wrapper');
406				$container.find('.hidden-paragraphs').slideDown();
407				$(this).hide();
408			  });
409
410			  // Collapse hidden content
411			  $(document).on('click', '.read-less-btn', function () {
412				const $container = $(this).closest('.wpb_wrapper');
413				$container.find('.hidden-paragraphs').slideUp();
414				$container.find('.read-more-btn').show();
415			  });
416	  
417	  
418	$('.review-quote-slider').on('init', function () {
419	  $(this).removeClass('slider-loading');
420	});
421	 $('.review-quote-slider').slick({
422	  slidesToShow: 1,
423	  dots:false,
424      arrows: true,
425	  infinite: false,
426	  autoplay: true,
427	  autoplaySpeed: 8000,
428	  prevArrow:"<button type='button' class='slick-prev pull-left'><i class='fa fa-angle-left' aria-hidden='true'></i></button>",
429      nextArrow:"<button type='button' class='slick-next pull-right'><i class='fa fa-angle-right' aria-hidden='true'></i></button>"
430	  });
431	
432	
433	 $('.rating span.w-post-elm-value').each(function () {
434		var text = $(this).text().toLowerCase().trim();
435		var stars = '';
436
437		if (text === 'comfort') {
438			stars = '3-Star';
439		} else if (text === 'budget') {
440			stars = '2-Star';
441		} else if (text === 'deluxe') {
442			stars = '4-Star';
443		}
444
445		if (stars !== '') {
446			$(this).text(stars);
447		}
448	});
449	
450	
451	
452});
453
454/*Route Template FAQ Accordion*/
455
456jQuery(document).ready(function($) {
457	
458	// Detect language from URL
459	var currentUrl = window.location.href;
460	var isSpanish = currentUrl.indexOf('/es/') !== -1;
461
462	var readMoreText = isSpanish ? "Leer más" : "Read More";
463	var readLessText = isSpanish ? "Leer menos" : "Read Less";
464	
465	const $faqRows = $('.route_faq .repeater-row');
466
467  // Append icon to each question
468  $('.route_faq .question').each(function() {
469    $(this).append('<i class="faq-icon far fa-plus"></i>');
470  });
471
472  // Open first row
473  //const $first = $faqRows.first();
474  //$first.addClass('active').find('.answer').slideDown();
475  //$first.find('.faq-icon').removeClass('fa-plus').addClass('fa-minus');
476
477  // On click
478  $('.route_faq .question').on('click', function() {
479    const $clickedRow = $(this).closest('.repeater-row');
480    const $icon = $(this).find('.faq-icon');
481
482    if (!$clickedRow.hasClass('active')) {
483      // Close all
484      $faqRows.removeClass('active').find('.answer').slideUp();
485      $('.faq-icon').removeClass('fa-minus').addClass('fa-plus');
486
487      // Open clicked
488      $clickedRow.addClass('active').find('.answer').slideDown();
489      $icon.removeClass('fa-plus').addClass('fa-minus');
490    } else {
491      // Toggle off if already open
492      $clickedRow.removeClass('active').find('.answer').slideUp();
493      $icon.removeClass('fa-minus').addClass('fa-plus');
494    }
495  });
496  
497  $("#faq-section-main .faq_content").each(function() {
498	
499	  
500    var $this = $(this);
501    var lineHeight = parseInt($this.css("line-height")) || 24; // fallback
502    var maxLines = 3;
503    var maxHeight = lineHeight * maxLines;
504
505    // Only add Read More if content is taller than 3 lines
506    if ($this[0].scrollHeight > maxHeight) {
507      $this.css({
508        "max-height": maxHeight,
509        "overflow": "hidden",
510        "transition": "max-height 0.4s ease"
511      });
512
513      // Add toggle link
514      $this.after('<span class="read-more faq_btn" style="cursor:pointer;color:#0073aa;">'+ readMoreText +'</span>');
515
516      $(document).on("click", ".read-more", function() {
517        var $btn = $(this);
518
519        if ($this.hasClass("expanded")) {
520          // collapse
521          $this.removeClass("expanded").css("max-height", maxHeight);
522          $btn.text(readMoreText);
523        } else {
524          // expand
525          $this.addClass("expanded").css("max-height", $this[0].scrollHeight);
526          $btn.text(readLessText);
527        }
528      });
529    }
530  });
531  
532  
533  
534});
535/*Route Template FAQ Accordion*/
536
537/*Route Rating Mobile Slider*/
538jQuery(document).ready(function($) {
539  function initMobileSlider() {
540    if ($(window).width() <= 991 && !$('.camino-top-head-right .stacking_default').hasClass('slick-initialized')) {
541      $('.camino-top-head-right .stacking_default').slick({
542        dots: false,
543        arrows: false,
544        infinite: true,
545		autoplay:true,
546        speed: 300,
547        slidesToShow: 2,
548        adaptiveHeight: true
549      });
550    } else if ($(window).width() > 600 && $('.camino-top-head-right .stacking_default').hasClass('slick-initialized')) {
551      $('.camino-top-head-right .stacking_default').slick('unslick');
552    }
553  }
554
555  // Initialize on load
556  initMobileSlider();
557
558  // Recheck on resize
559  $(window).on('resize', function() {
560    initMobileSlider();
561  });
562  
563  
564
565});
566
567
568jQuery(document).ready(function($) {
569
570$('#map-info-view-tbn').click(function () {
571    $(".map_info_main").css("display", function (i, currentDisplay) {
572        if (currentDisplay === "none") {
573            return "inline-block";
574        } else {
575            return "none";
576        }
577    });
578
579    // Toggle button text
580    let label = $(this).find('.w-btn-label');
581    let currentText = label.text().trim();
582    label.text(currentText === 'Read Less' ? 'Read More' : 'Read Less');
583});
584
585/*Wrap anchor tag before image gallery*/
586 
587$('#route-gallery-section .w-post-elm.post_image img').each(function() {
588    var img = $(this);
589    var originalSrc = img.attr('src');
590
591    // Remove size suffix like -225x225 (but keep extension)
592    var fullImgSrc = originalSrc.replace(/-\d+x\d+(?=\.(jpg|jpeg|png|gif|webp))/i, '');
593
594    // Wrap with anchor tag if not already wrapped
595    if (!img.parent('a.fancybox').length) {
596      img.wrap('<a href="' + fullImgSrc + '" data-fancybox="gallery" class="fancybox"></a>');
597    }
598  });
599});
600
601
602
603
604/*Guided Groups Book Now Script*/
605jQuery(document).ready(function($) {
606  $('.custom_booking_btn a:not(.enq_now_link)').on('click', function(e) {
607    e.preventDefault();
608	const $row = $(this).closest('.custom_booking_row');
609	const tourID = $row.find('.GuidedTourID').val();
610	const RoutePageID = $('.RoutePageID').val();
611	
612    const date = $row.find('.custom_booking_date').text().trim();
613    const availability = $row.find('.booking_slot').text().trim();
614	
615	const baseUrl = $(this).attr('href');
616    const newUrl = baseUrl + 
617      '?tour_id=' + encodeURIComponent(tourID) +
618      '&date=' + encodeURIComponent(date) +
619	  '&RoutePageID=' + encodeURIComponent(RoutePageID) +
620      '&availability=' + encodeURIComponent(availability);
621
622    // Redirect
623    window.location.href = newUrl;
624  });
625  
626  $('.tour-list-guided').each(function() {
627		const $slider = $(this);
628
629		$slider.on('init', function(event, slick){
630			// Add margin-right to each slide after Slick initializes
631			$slider.find('.slick-slide').css('margin-right', '20px'); // ← Change spacing here
632		});
633
634		$slider.slick({
635			slidesToShow: 3,
636			slidesToScroll: 1,
637			infinite: false,
638			arrows: false,
639			dots: false,
640			autoplay: true,
641			responsive: [
642				{
643					breakpoint: 768,
644					settings: {
645						slidesToShow: 1
646					}
647				}
648			]
649		});
650	});
651	
652	/*Travel Agent Testimonial Slider*/
653	$('.travel_agent_testimonial_list .repeater').slick({
654	  slidesToShow: 1,
655	  dots:false,
656      arrows: true,
657	  infinite: false,
658	  autoplay: false,
659	  adaptiveHeight: true,
660	  prevArrow:"<button type='button' class='slick-prev pull-left'><i class='fa fa-angle-left' aria-hidden='true'></i></button>",
661      nextArrow:"<button type='button' class='slick-next pull-right'><i class='fa fa-angle-right' aria-hidden='true'></i></button>"
662	});
663});
664
665
666
667/*Tour Pages FAQ Script*/
668jQuery(document).ready(function($) {
669  const $faqWrapper = $('.tour-faq');
670  const $faqRows = $('.tour-faq-list .repeater-row');
671
672  // Hide section if no FAQ rows
673  if ($faqRows.length === 0) {
674    $faqWrapper.hide();
675    return; // Stop script
676  }
677
678  $('.tour-faq-list .question').each(function() {
679    $(this).append('<i class="faq-icon far fa-plus"></i>');
680  });
681
682  $('.tour-faq-list .question').on('click', function() {
683    const $clickedRow = $(this).closest('.repeater-row');
684    const $icon = $(this).find('.faq-icon');
685
686    if (!$clickedRow.hasClass('active')) {
687      $faqRows.removeClass('active').find('.answer').slideUp();
688      $('.faq-icon').removeClass('fa-minus').addClass('fa-plus');
689
690      $clickedRow.addClass('active').find('.answer').slideDown();
691      $icon.removeClass('fa-plus').addClass('fa-minus');
692    } else {
693      $clickedRow.removeClass('active').find('.answer').slideUp();
694      $icon.removeClass('fa-minus').addClass('fa-plus');
695    }
696  });
697});
698/*End Tour Pages FAQ Script*/
699
700
701
702/*******Customer Area Login Script******/
703jQuery(document).ready(function($) {
704    const apiLoginUrl = "https://api.followthecamino.com/api/login_check";
705    const apiUserUrl = "https://api.followthecamino.com/api/user_info";
706    const tokenKey = "jwtToken";
707	const tokenUserKey = "jwtUser";
708    const customerLandingUrl = "https://customer.followthecamino.com/auth/auth-token?token=";
709
710    // Check for existing token — if valid, don’t redirect automatically
711    const existingToken = localStorage.getItem(tokenKey);
712    if (existingToken) {
713        validateToken(existingToken); // Only validate, no redirect
714    }
715
716    // Handle login form submit
717    $("#ftcLoginForm").on("submit", function(e) {
718        e.preventDefault();
719        $("#ftc_message").empty();
720        $("#ftc_emailError, #ftc_passwordError").empty();
721        $("#ftc_username, #ftc_password").css("border-color", "#ddd");
722
723        const username = $("#ftc_username").val().trim();
724        const password = $("#ftc_password").val().trim();
725        let isValid = true;
726
727        // Inline validation
728        if (username === "") {
729            $("#ftc_emailError").text("Email is required.");
730            $("#ftc_username").css("border-color", "#e74c3c");
731            isValid = false;
732        } else if (!validateEmail(username)) {
733            $("#ftc_emailError").text("Please enter a valid email.");
734            $("#ftc_username").css("border-color", "#e74c3c");
735            isValid = false;
736        }
737
738        if (password === "") {
739            $("#ftc_passwordError").text("Password is required.");
740            $("#ftc_password").css("border-color", "#e74c3c");
741            isValid = false;
742        }
743
744        if (!isValid) return;
745
746        // AJAX login request
747        $.ajax({
748            url: apiLoginUrl,
749            type: "POST",
750            contentType: "application/json",
751            data: JSON.stringify({
752                username: username,
753                password: password
754            }),
755            beforeSend: function() {
756                $("#ftc_message").html("<div style='color:#3498db;padding:8px;'>Logging in...</div>");
757            },
758            success: function(response) {
759                // Save token to localStorage
760                localStorage.setItem(tokenKey, response.token);
761				
762                $("#ftc_message").html("<div style='color:#2ecc71;padding:8px;border-radius:5px;'>Login successful! Redirecting...</div>");
763
764                // Redirect once to customer area
765                setTimeout(() => {
766                    window.location.href = customerLandingUrl + encodeURIComponent(response.token);
767                }, 1000);
768            },
769            error: function(xhr) {
770                localStorage.removeItem(tokenKey);
771				localStorage.removeItem(tokenUserKey);
772                $("#ftc_message").html("<div style='color:#e74c3c;padding:8px;border-radius:5px;'>Invalid email or password. Please try again.</div>");
773            }
774        });
775    });
776
777    // Token validation
778    function validateToken(token) {
779        $.ajax({
780            url: apiUserUrl,
781            type: "GET",
782            headers: {
783                "Authorization": "Bearer " + token,
784                "Content-Type": "application/json"
785            },
786            success: function(userData) {
787                console.log("User verified:", userData.username);
788				localStorage.setItem(tokenUserKey, userData.firstName);
789                // User token is valid, do nothing (stay on current page)
790            },
791            error: function() {
792                // Token invalid — clear it
793                localStorage.removeItem(tokenKey);
794				localStorage.removeItem(tokenUserKey);
795                console.log("Token expired or invalid, cleared from storage.");
796            }
797        });
798    }
799
800	function validateEmail(email) {
801	const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
802	return re.test(String(email).toLowerCase());
803	}
804
805	//localStorage.removeItem(tokenKey);
806
807});
808
809
810
811/*Check User Info*/
812jQuery(document).ready(function($) {
813    const $loginBtn = $('#LoginCustomer');
814	const $loginBtnMB = $('#MBLoginCustomer');
815    const token = localStorage.getItem('jwtToken');
816
817    if (token) {
818        $.ajax({
819            url: 'https://api.followthecamino.com/api/user_info',
820            type: 'GET',
821            headers: {
822                'Authorization': 'Bearer ' + token
823            },
824            success: function(response) {
825				/*if (response) {
826                    console.log('✅ Valid token — user info:', response);
827                }*/
828                if (response && response.username) {
829                    // ✅ Valid token
830                    $loginBtn
831                        .removeClass('customer-login icon_atleft pum-trigger')
832						.addClass('logged-in-user')
833                        .data('url', 'https://customer.followthecamino.com/landing');
834                    $loginBtn.find('.w-btn-label').text(response.firstName);
835					
836					$loginBtnMB
837                        .removeClass('customer-login icon_atleft pum-trigger')
838                        .addClass('logged-in-user')
839                        .data('url', 'https://customer.followthecamino.com/landing');
840                    $loginBtnMB.find('.w-btn-label').text(response.firstName);
841                } else {
842                    // ❌ Invalid response
843                    resetLoginButton();
844                }
845            },
846            error: function() {
847                // ❌ Token invalid or expired
848                resetLoginButton();
849            }
850        });
851    } else {
852        // ❌ No token stored
853        resetLoginButton();
854    }
855
856    // Helper: revert button back to login state
857    function resetLoginButton() {
858        $loginBtn
859            .addClass('customer-login icon_atleft pum-trigger')
860             .removeClass('logged-in-user')
861			 .removeData('url');
862        $loginBtn.find('.w-btn-label').text('Client Login');
863		
864		$loginBtnMB
865            .addClass('customer-login icon_atleft pum-trigger')
866             .removeClass('logged-in-user')
867			 .removeData('url');
868        $loginBtnMB.find('.w-btn-label').text('Client Login');
869    }
870	
871	 $(document).on('click', '.logged-in-user', function () {
872        const url = $(this).data('url');
873        if (url) {
874            window.location.href = url;
875        }
876    });
877});
878/*******Customer Area Login Script END ******/

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.