PageSourceSearch

https://laparva.cl/wp-content/themes/laparva/js/main.js?ver=1753242729-2516

js laparva.cl collected 2026-10-02 10:21:19 UTC 52,592 bytes, 1,467 lines download raw bytes

1document.addEventListener('DOMContentLoaded', function() {
2  // Mobile Menu Logic
3  const mobileMenuToggle = document.getElementById('mobile-menu-toggle');
4  const mobileMenuOverlay = document.getElementById('mobile-menu-overlay');
5  const mobileMenuClose = document.getElementById('mobile-menu-close');
6  const mainLevel = mobileMenuOverlay.querySelector('.mobile-menu-main-level');
7  const nextButtons = mobileMenuOverlay.querySelectorAll('.mobile-menu-next');
8  const backButtons = mobileMenuOverlay.querySelectorAll('.mobile-menu-back');
9  const allLinks = mobileMenuOverlay.querySelectorAll('a');
10
11  if (mobileMenuToggle && mobileMenuOverlay && mobileMenuClose) {
12    mobileMenuToggle.addEventListener('click', () => {
13      mobileMenuOverlay.hidden = false;
14      mobileMenuOverlay.classList.add('is-open');
15      document.body.style.overflow = 'hidden'; // Prevent scrolling of the page
16    });
17
18    mobileMenuClose.addEventListener('click', closeMenu);
19
20    allLinks.forEach(link => {
21      link.addEventListener('click', (e) => {
22        // If it's a link with a valid href, close the menu
23        // We don't want to close for buttons that behave like links
24        if (link.getAttribute('href') && link.getAttribute('href') !== '#') {
25          closeMenu();
26        }
27      });
28    });
29  }
30  
31  function closeMenu() {
32    mobileMenuOverlay.classList.remove('is-open');
33    document.body.style.overflow = ''; // Restore scrolling
34
35    // Use transitionend event to hide the element after transition
36    mobileMenuOverlay.addEventListener('transitionend', () => {
37      mobileMenuOverlay.hidden = true;
38    }, { once: true });
39
40    // Reset all sub-menus
41    mobileMenuOverlay.querySelectorAll('.mobile-menu-sub-level').forEach(sub => {
42      sub.hidden = true;
43      sub.classList.remove('is-active');
44    });
45    mainLevel.classList.remove('is-hidden');
46  }
47
48  nextButtons.forEach(button => {
49    button.addEventListener('click', () => {
50      const targetId = button.dataset.target;
51      const targetLevel = document.getElementById(targetId);
52      if (targetLevel) {
53        mainLevel.classList.add('is-hidden');
54        targetLevel.hidden = false;
55        // Timeout to allow the element to be 'block' before adding the class for transition
56        setTimeout(() => {
57          targetLevel.classList.add('is-active');
58        }, 10);
59      }
60    });
61  });
62
63  backButtons.forEach(button => {
64    button.addEventListener('click', () => {
65      const currentLevel = button.closest('.mobile-menu-sub-level');
66      if (currentLevel) {
67        mainLevel.classList.remove('is-hidden');
68        currentLevel.classList.remove('is-active');
69        // Use transitionend to hide after transition
70        currentLevel.addEventListener('transitionend', () => {
71          currentLevel.hidden = true;
72        }, { once: true });
73      }
74    });
75  });
76
77  // Mobile Menu Accordion
78  const accordionToggles = mobileMenuOverlay.querySelectorAll('.mobile-accordion-toggle');
79  accordionToggles.forEach(toggle => {
80    toggle.addEventListener('click', () => {
81      const panel = toggle.nextElementSibling;
82      const arrow = toggle.querySelector('.arrow');
83
84      if (panel && panel.classList.contains('mobile-accordion-panel')) {
85        panel.hidden = !panel.hidden;
86        arrow.classList.toggle('is-open');
87      }
88    });
89  });
90
91  // Handle all language selectors (both in header and footer)
92  const langSelectors = document.querySelectorAll('.lang-selector');
93  
94  langSelectors.forEach(langSelector => {
95    const currentLang = langSelector.querySelector('.current-lang');
96    const langOptions = langSelector.querySelector('.lang-options');
97    
98    if (currentLang && langOptions) {
99      // Toggle dropdown on click of the current language display
100      currentLang.addEventListener('click', function(event) {
101        event.stopPropagation(); // Prevent click from immediately closing dropdown
102        langSelector.classList.toggle('active');
103      });
104
105      // Handle clicks on language options
106      langOptions.addEventListener('click', function(event) {
107        if (event.target.tagName === 'A') {
108          // Check if the link has no translation
109          if (event.target.classList.contains('no-translation')) {
110            event.preventDefault(); // Prevent navigation for missing translations
111            
112            // Show a temporary message to the user
113            const originalText = event.target.textContent;
114            event.target.textContent = 'No disponible';
115            
116            setTimeout(() => {
117              event.target.textContent = originalText;
118            }, 1500);
119            
120            console.log('Translation not available for:', event.target.textContent);
121            langSelector.classList.remove('active'); // Close dropdown
122            return;
123          }
124          
125          // If it's the current language, just close the dropdown
126          if (event.target.classList.contains('current')) {
127            event.preventDefault();
128            langSelector.classList.remove('active');
129            return;
130          }
131          
132          // For valid language links, allow navigation and close dropdown
133          console.log('Switching to language:', event.target.textContent);
134          langSelector.classList.remove('active');
135          // Navigation will proceed normally via href
136        }
137      });
138    }
139  });
140
141  // Close language dropdowns if clicking outside of them
142  document.addEventListener('click', function(event) {
143    langSelectors.forEach(langSelector => {
144      if (!langSelector.contains(event.target)) {
145        langSelector.classList.remove('active');
146      }
147    });
148  });
149
150  // Mega Menu Logic
151  const megaMenuToggles = document.querySelectorAll('.mega-menu-toggle');
152  const megaMenus = document.querySelectorAll('.mega-menu');
153  const megaMenuContainer = document.getElementById('mega-menu-container');
154  let openMenu = null; // Track the currently open menu
155
156  // Function to close the currently open menu
157  const closeOpenMenu = () => {
158    if (openMenu) {
159      const currentToggle = document.querySelector(`.mega-menu-toggle[data-target="${openMenu.id}"]`);
160      if (currentToggle) {
161        currentToggle.classList.remove('active');
162      }
163      openMenu.style.display = 'none';
164      openMenu.classList.remove('active'); // Ensure class is removed
165      openMenu = null;
166    }
167  };
168
169  megaMenuToggles.forEach(toggle => {
170    toggle.addEventListener('click', (event) => {
171      event.preventDefault(); // Prevent any default button action
172      event.stopPropagation(); // Stop click from propagating to document listener immediately
173
174      const targetId = toggle.getAttribute('data-target');
175      const targetMenu = document.getElementById(targetId);
176
177      if (!targetMenu) return; // Exit if target menu doesn't exist
178
179      // If the clicked menu is already open, close it
180      if (openMenu === targetMenu) {
181        closeOpenMenu();
182      } else {
183        // Close any other open menu first
184        closeOpenMenu();
185
186        // Open the new menu
187        toggle.classList.add('active');
188        targetMenu.style.display = 'block';
189        targetMenu.classList.add('active'); // Add class for CSS styling if needed
190        openMenu = targetMenu;
191      }
192    });
193  });
194
195  // Close menu when clicking outside the menu or toggles
196  document.addEventListener('click', (event) => {
197    const clickedElement = event.target;
198    // Check if the click was on one of the toggle buttons
199    const isClickOnToggle = clickedElement.closest('.mega-menu-toggle');
200    // Check if the click was inside the currently open mega menu
201    const isClickInsideMegaMenu = openMenu && openMenu.contains(clickedElement);
202
203    // If a menu is open, and the click was NOT on a toggle button AND NOT inside the open menu, then close it.
204    if (openMenu && !isClickOnToggle && !isClickInsideMegaMenu) {
205      closeOpenMenu();
206    }
207  });
208
209  // Close menu when clicking a link inside the menu
210  if (megaMenuContainer) {
211    megaMenuContainer.addEventListener('click', (event) => {
212      if (event.target.tagName === 'A') {
213        closeOpenMenu();
214        // Allow the link navigation to proceed
215      }
216    });
217  }
218
219  // Discounts slider logic
220  const discountsSlider = document.querySelector('.discounts__slider');
221  if (discountsSlider) {
222    const slidesContainer = discountsSlider.querySelector('.discounts__slides');
223    const prevBtn = discountsSlider.querySelector('.discounts__slider-arrow--prev');
224    const nextBtn = discountsSlider.querySelector('.discounts__slider-arrow--next');
225
226    const updateButtons = () => {
227      if (!slidesContainer) return;
228      const maxScrollLeft = slidesContainer.scrollWidth - slidesContainer.clientWidth;
229      prevBtn.disabled = slidesContainer.scrollLeft <= 0;
230      nextBtn.disabled = slidesContainer.scrollLeft >= maxScrollLeft;
231    };
232
233    const scroll = (amount) => {
234      if (!slidesContainer) return;
235      slidesContainer.scrollBy({ left: amount, behavior: 'smooth' });
236    };
237
238    if (slidesContainer && prevBtn && nextBtn) {
239      prevBtn.addEventListener('click', () => {
240        const slideWidth = slidesContainer.querySelector('.discounts__slide').clientWidth;
241        scroll(-slideWidth);
242      });
243
244      nextBtn.addEventListener('click', () => {
245        const slideWidth = slidesContainer.querySelector('.discounts__slide').clientWidth;
246        scroll(slideWidth);
247      });
248
249      slidesContainer.addEventListener('scroll', updateButtons);
250      window.addEventListener('resize', updateButtons);
251
252      // Initial state
253      updateButtons();
254    }
255  }
256
257  // =============================
258  // Eventos Slider Logic (Frontpage)
259  // =============================
260  const eventosSlider = document.querySelector('.eventos__slider');
261  if (eventosSlider) {
262    const track = eventosSlider.querySelector('.eventos__slider-track');
263    const cards = Array.from(track.children);
264    const prevBtn = eventosSlider.querySelector('.eventos__slider-btn--prev');
265    const nextBtn = eventosSlider.querySelector('.eventos__slider-btn--next');
266    const scrollbarPlaceholder = eventosSlider.querySelector('.eventos__scrollbar-placeholder');
267
268    // Create custom scrollbar elements
269    let rail = scrollbarPlaceholder.querySelector('.eventos__scrollbar-rail');
270    let handle = scrollbarPlaceholder.querySelector('.eventos__scrollbar-handle');
271    if (!rail) {
272      rail = document.createElement('div');
273      rail.className = 'eventos__scrollbar-rail';
274      scrollbarPlaceholder.appendChild(rail);
275    }
276    if (!handle) {
277      handle = document.createElement('div');
278      handle.className = 'eventos__scrollbar-handle';
279      scrollbarPlaceholder.appendChild(handle);
280    }
281
282    let cardWidth = 0;
283    let gap = 0;
284    let visibleCards = 2.5;
285    let maxScroll = 0;
286    let maxIndex = 0;
287    let currentIndex = 0;
288    
289    // Touch handling variables
290    let isDragging = false;
291    let startPos = 0;
292    let currentTranslate = 0;
293    let prevTranslate = 0;
294    let animationID = 0;
295    let currentPosition = 0;
296
297    function setupSlider() {
298      if (cards.length === 0) return;
299      
300      // Reset to initial position
301      track.scrollLeft = 0;
302      currentIndex = 0;
303      
304      // Calculate visible cards and max index based on screen size
305      if (window.innerWidth <= 576) {
306        visibleCards = 1.2; // Show just over 1 card on mobile
307        maxIndex = Math.max(0, cards.length - 1);
308      } else if (window.innerWidth <= 768) {
309        visibleCards = 1.5;
310        maxIndex = Math.max(0, cards.length - Math.floor(visibleCards));
311      } else {
312        visibleCards = 2.5;
313        maxIndex = Math.max(0, cards.length - Math.floor(visibleCards));
314      }
315      
316      updateButtons();
317      updateScrollbar();
318    }
319
320    function scrollToIndex(index) {
321      currentIndex = Math.max(0, Math.min(index, maxIndex));
322      
323      // Use CSS scrolling to the card at the specified index
324      if (currentIndex < cards.length) {
325        const card = cards[currentIndex];
326        
327        // Use native scrollIntoView for smooth CSS-based scrolling
328        card.scrollIntoView({
329          behavior: 'smooth',
330          block: 'nearest',
331          inline: 'start'
332        });
333      }
334      
335      updateButtons();
336      updateScrollbar();
337    }
338
339    function updateButtons() {
340      prevBtn.disabled = currentIndex <= 0;
341      nextBtn.disabled = currentIndex >= maxIndex;
342    }
343
344    function updateScrollbar() {
345      const containerWidth = eventosSlider.offsetWidth;
346      const trackWidth = track.scrollWidth;
347      const handleWidth = Math.max(containerWidth * (containerWidth / trackWidth), 40);
348      handle.style.width = handleWidth + 'px';
349      const scrollLeft = track.scrollLeft;
350      const maxScrollLeft = trackWidth - containerWidth;
351      const left = maxScrollLeft > 0 ? (scrollLeft / maxScrollLeft) * (containerWidth - handleWidth) : 0;
352      handle.style.left = left + 'px';
353    }
354
355    prevBtn.addEventListener('click', function() {
356      scrollToIndex(currentIndex - 1);
357    });
358    nextBtn.addEventListener('click', function() {
359      scrollToIndex(currentIndex + 1);
360    });
361    
362    // Add touch support for mobile
363    let startY = 0;
364    let isHorizontalSwipe = false;
365    
366    function touchStart(event) {
367      if (!cards.length) return;
368      
369      isDragging = true;
370      startPos = getPositionX(event);
371      startY = getPositionY(event);
372      currentPosition = currentTranslate;
373      isHorizontalSwipe = false;
374      
375      // Stop any momentum scrolling
376      cancelAnimationFrame(animationID);
377      
378      // Save current transform value
379      const transformMatrix = window.getComputedStyle(track).getPropertyValue('transform');
380      if (transformMatrix !== 'none') {
381        currentTranslate = parseInt(transformMatrix.split(',')[4].trim());
382        prevTranslate = currentTranslate;
383      }
384      
385      track.style.transition = 'none';
386    }
387    
388    function touchMove(event) {
389      if (isDragging) {
390        const currentX = getPositionX(event);
391        const currentY = getPositionY(event);
392        const diffX = currentX - startPos;
393        const diffY = currentY - startY;
394        
395        // Determine if this is a horizontal swipe
396        if (!isHorizontalSwipe && Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > 10) {
397          isHorizontalSwipe = true;
398        }
399        
400        // If it's a horizontal swipe, prevent default scrolling and handle slider movement
401        if (isHorizontalSwipe) {
402          event.preventDefault();
403          event.stopPropagation();
404          
405          currentTranslate = prevTranslate + diffX;
406          
407          // Add resistance at the edges
408          if (currentTranslate > 0) {
409            currentTranslate = currentTranslate / 3;
410          } else if (currentTranslate < -(track.scrollWidth - eventosSlider.offsetWidth)) {
411            const overscroll = Math.abs(currentTranslate) - (track.scrollWidth - eventosSlider.offsetWidth);
412            currentTranslate = -(track.scrollWidth - eventosSlider.offsetWidth) - (overscroll / 3);
413          }
414          
415          track.style.transform = `translateX(${currentTranslate}px)`;
416        } else if (Math.abs(diffY) > 10) {
417          // If it's clearly a vertical swipe, allow default behavior and stop slider interaction
418          isDragging = false;
419        }
420      }
421    }
422    
423    function touchEnd() {
424      if (!isDragging) return;
425      
426      isDragging = false;
427      
428      // Only handle slider snapping if it was a horizontal swipe
429      if (isHorizontalSwipe) {
430        const movedBy = currentTranslate - prevTranslate;
431        
432        // More sensitive thresholds for mobile - smaller movements should trigger slides
433        const threshold = window.innerWidth <= 576 ? 30 : 50;
434        
435        // Determine if slide should snap to next or previous based on move distance
436        if (movedBy < -threshold && currentIndex < maxIndex) {
437          // Moved left - go to next card
438          currentIndex += 1;
439        } else if (movedBy > threshold && currentIndex > 0) {
440          // Moved right - go to previous card
441          currentIndex -= 1;
442        }
443        
444        // Reset transition for smooth animation
445        track.style.transition = 'transform 0.3s ease';
446        
447        // Snap to closest card
448        scrollToIndex(currentIndex);
449      } else {
450        // If no significant horizontal movement, snap back to current position
451        track.style.transition = 'transform 0.3s ease';
452        scrollToIndex(currentIndex);
453      }
454      
455      // Reset swipe detection
456      isHorizontalSwipe = false;
457    }
458    
459    function getPositionX(event) {
460      return event.type.includes('mouse') ? event.pageX : event.touches[0].clientX;
461    }
462    
463    function getPositionY(event) {
464      return event.type.includes('mouse') ? event.pageY : event.touches[0].clientY;
465    }
466    
467    // Add mouse/touch event listeners - disabled for CSS scrolling approach
468    // track.addEventListener('mousedown', touchStart);
469    // track.addEventListener('touchstart', touchStart, { passive: true });
470    
471    // window.addEventListener('mousemove', touchMove);
472    // window.addEventListener('touchmove', touchMove, { passive: false });
473    
474    // window.addEventListener('mouseup', touchEnd);
475    // window.addEventListener('touchend', touchEnd);
476    
477    // Prevent context menu on long press
478    track.addEventListener('contextmenu', e => {
479      if (e.target.closest('.eventos__card')) {
480        return true; // Allow context menu on card links
481      }
482      e.preventDefault();
483      e.stopPropagation();
484      return false;
485    });
486    
487    // Add scroll event listener for CSS scrolling
488    track.addEventListener('scroll', function() {
489      updateScrollbar();
490    });
491
492    window.addEventListener('resize', function() {
493      setupSlider();
494      updateScrollbar();
495    });
496    
497    // Initial setup
498    setupSlider();
499    updateScrollbar();
500  }
501
502  // =============================
503  // Alojamiento Gallery Slider Logic
504  // =============================
505  const alojamientoGallerySlider = document.querySelector('.alojamiento-gallery__slider');
506  if (alojamientoGallerySlider) {
507    const track = alojamientoGallerySlider.querySelector('.alojamiento-gallery__slider-track');
508    const cards = Array.from(track.children);
509    const prevBtn = alojamientoGallerySlider.querySelector('.alojamiento-gallery__slider-btn--prev');
510    const nextBtn = alojamientoGallerySlider.querySelector('.alojamiento-gallery__slider-btn--next');
511    const scrollbarPlaceholder = alojamientoGallerySlider.querySelector('.alojamiento-gallery__scrollbar-placeholder');
512
513    // Create custom scrollbar elements
514    let rail = document.createElement('div');
515    rail.className = 'alojamiento-gallery__scrollbar-rail';
516    scrollbarPlaceholder.appendChild(rail);
517    
518    let handle = document.createElement('div');
519    handle.className = 'alojamiento-gallery__scrollbar-handle';
520    scrollbarPlaceholder.appendChild(handle);
521
522    let cardWidth = 0;
523    let gap = 0;
524    let visibleCards = 2; // Show 2 cards at a time due to larger card size
525    let maxScroll = 0;
526    let maxIndex = 0;
527    let currentIndex = 0;
528
529    function setupSlider() {
530      if (cards.length === 0) return;
531      
532      // Calculate card width and gap
533      cardWidth = cards[0].offsetWidth;
534      if (cards.length > 1) {
535        gap = cards[1].offsetLeft - cards[0].offsetLeft - cardWidth;
536      } else {
537        gap = 20;
538      }
539      
540      // Reset transform to initial position
541      track.style.transform = 'translateX(0)';
542      currentIndex = 0;
543      
544      // Each card represents one position
545      maxIndex = cards.length - 1;
546      
547      updateButtons();
548      updateScrollbar();
549    }
550
551    function scrollToIndex(index) {
552      currentIndex = Math.max(0, Math.min(index, maxIndex));
553      
554      // Simply scroll to the card at the specified index
555      if (currentIndex < cards.length) {
556        const card = cards[currentIndex];
557        const offsetLeft = card.offsetLeft;
558        
559        // Use transform to position the track
560        track.style.transform = `translateX(-${offsetLeft}px)`;
561      }
562      
563      updateButtons();
564      updateScrollbar();
565    }
566
567    function updateButtons() {
568      prevBtn.disabled = currentIndex <= 0;
569      nextBtn.disabled = currentIndex >= maxIndex;
570    }
571
572    function updateScrollbar() {
573      const containerWidth = alojamientoGallerySlider.offsetWidth;
574      const trackWidth = track.scrollWidth;
575      const handleWidth = Math.max(containerWidth * (containerWidth / trackWidth), 40);
576      handle.style.width = handleWidth + 'px';
577      
578      if (currentIndex === 0) {
579        handle.style.left = '0px';
580        return;
581      }
582      
583      if (currentIndex === maxIndex) {
584        handle.style.left = (containerWidth - handleWidth) + 'px';
585        return;
586      }
587      
588      // Calculate progress based on current index
589      const progress = currentIndex / maxIndex;
590      const left = progress * (containerWidth - handleWidth);
591      handle.style.left = left + 'px';
592    }
593
594    prevBtn.addEventListener('click', function() {
595      scrollToIndex(currentIndex - 1);
596    });
597    nextBtn.addEventListener('click', function() {
598      scrollToIndex(currentIndex + 1);
599    });
600    
601    // Remove scroll event listener since we're using transform
602    // instead add a transitionend listener to update scrollbar
603    track.addEventListener('transitionend', updateScrollbar);
604    
605    window.addEventListener('resize', function() {
606      setupSlider();
607      updateScrollbar();
608    });
609    
610    // Initial setup
611    track.style.transition = 'transform 0.3s ease';
612    setupSlider();
613    updateScrollbar();
614  }
615
616  // =============================
617  // Alojamiento Fechas Datepickers
618  // =============================
619  const fechaEntrada = document.getElementById('fecha-entrada');
620  const fechaSalida = document.getElementById('fecha-salida');
621  
622  // =============================
623  // Skiday Single Date Picker
624  // =============================
625  const fechaReserva = document.getElementById('fecha-reserva');
626
627  if (fechaEntrada && fechaSalida) {
628    // Create date picker instances
629    let entradaPickerVisible = false;
630    let salidaPickerVisible = false;
631    
632    // Create calendar elements
633    const entradaCalendar = document.createElement('div');
634    entradaCalendar.className = 'date-calendar entrada-calendar';
635    entradaCalendar.style.display = 'none';
636    entradaCalendar.style.position = 'absolute';
637    entradaCalendar.style.zIndex = '1000';
638    entradaCalendar.style.backgroundColor = '#fff';
639    entradaCalendar.style.boxShadow = '0 4px 12px rgba(0,0,0,0.1)';
640    entradaCalendar.style.borderRadius = '8px';
641    entradaCalendar.style.padding = '16px';
642    entradaCalendar.style.width = '280px';
643    
644    const salidaCalendar = document.createElement('div');
645    salidaCalendar.className = 'date-calendar salida-calendar';
646    salidaCalendar.style.display = 'none';
647    salidaCalendar.style.position = 'absolute';
648    salidaCalendar.style.zIndex = '1000';
649    salidaCalendar.style.backgroundColor = '#fff';
650    salidaCalendar.style.boxShadow = '0 4px 12px rgba(0,0,0,0.1)';
651    salidaCalendar.style.borderRadius = '8px';
652    salidaCalendar.style.padding = '16px';
653    salidaCalendar.style.width = '280px';
654    
655    // Append calendars to DOM - add to parent of inputs for better positioning
656    const formContainer = document.querySelector('.alojamiento-reserva__form');
657    formContainer.appendChild(entradaCalendar);
658    formContainer.appendChild(salidaCalendar);
659    
660    // Helper to create a simple calendar UI
661    function generateCalendarUI(date, calendarElement, onSelectCallback) {
662      // Clear previous content
663      calendarElement.innerHTML = '';
664      
665      const year = date.getFullYear();
666      const month = date.getMonth();
667      
668      // Create calendar header
669      const header = document.createElement('div');
670      header.className = 'calendar-header';
671      header.style.display = 'flex';
672      header.style.justifyContent = 'space-between';
673      header.style.marginBottom = '12px';
674      
675      const prevMonth = document.createElement('button');
676      prevMonth.innerHTML = '&lt;';
677      prevMonth.style.border = 'none';
678      prevMonth.style.background = 'none';
679      prevMonth.style.fontSize = '16px';
680      prevMonth.style.cursor = 'pointer';
681      
682      const monthTitle = document.createElement('div');
683      monthTitle.textContent = new Date(year, month, 1).toLocaleDateString('es-ES', { month: 'long', year: 'numeric' });
684      monthTitle.style.fontSize = '16px';
685      monthTitle.style.fontWeight = 'bold';
686      
687      const nextMonth = document.createElement('button');
688      nextMonth.innerHTML = '&gt;';
689      nextMonth.style.border = 'none';
690      nextMonth.style.background = 'none';
691      nextMonth.style.fontSize = '16px';
692      nextMonth.style.cursor = 'pointer';
693      
694      header.appendChild(prevMonth);
695      header.appendChild(monthTitle);
696      header.appendChild(nextMonth);
697      
698      // Create calendar grid
699      const grid = document.createElement('div');
700      grid.className = 'calendar-grid';
701      grid.style.display = 'grid';
702      grid.style.gridTemplateColumns = 'repeat(7, 1fr)';
703      grid.style.gap = '8px';
704      
705      // Days of week
706      const daysOfWeek = ['D', 'L', 'M', 'X', 'J', 'V', 'S'];
707      daysOfWeek.forEach(day => {
708        const dayElem = document.createElement('div');
709        dayElem.textContent = day;
710        dayElem.style.textAlign = 'center';
711        dayElem.style.fontSize = '14px';
712        dayElem.style.fontWeight = 'bold';
713        grid.appendChild(dayElem);
714      });
715      
716      // Calculate first day of month and number of days
717      const firstDay = new Date(year, month, 1).getDay();
718      const daysInMonth = new Date(year, month + 1, 0).getDate();
719      
720      // Add empty cells for days before first of month
721      for (let i = 0; i < firstDay; i++) {
722        const emptyDay = document.createElement('div');
723        grid.appendChild(emptyDay);
724      }
725      
726      // Add days of month
727      for (let day = 1; day <= daysInMonth; day++) {
728        const dayElem = document.createElement('div');
729        dayElem.textContent = day;
730        dayElem.style.textAlign = 'center';
731        dayElem.style.padding = '6px';
732        dayElem.style.cursor = 'pointer';
733        dayElem.style.borderRadius = '4px';
734        
735        // Highlight current date
736        const currentDate = new Date();
737        if (day === currentDate.getDate() && month === currentDate.getMonth() && year === currentDate.getFullYear()) {
738          dayElem.style.backgroundColor = '#e6f7ff';
739        }
740        
741        dayElem.addEventListener('mouseover', () => {
742          dayElem.style.backgroundColor = '#f0f0f0';
743        });
744        
745        dayElem.addEventListener('mouseout', () => {
746          if (!(day === currentDate.getDate() && month === currentDate.getMonth() && year === currentDate.getFullYear())) {
747            dayElem.style.backgroundColor = '';
748          }
749        });
750        
751        dayElem.addEventListener('click', () => {
752          const selectedDate = new Date(year, month, day);
753          onSelectCallback(selectedDate);
754        });
755        
756        grid.appendChild(dayElem);
757      }
758      
759      // Event listeners for month navigation
760      prevMonth.addEventListener('click', () => {
761        const prevMonthDate = new Date(year, month - 1, 1);
762        generateCalendarUI(prevMonthDate, calendarElement, onSelectCallback);
763      });
764      
765      nextMonth.addEventListener('click', () => {
766        const nextMonthDate = new Date(year, month + 1, 1);
767        generateCalendarUI(nextMonthDate, calendarElement, onSelectCallback);
768      });
769      
770      calendarElement.appendChild(header);
771      calendarElement.appendChild(grid);
772    }
773    
774    // Format date for display
775    function formatDate(date) {
776      return date.toLocaleDateString('es-ES', { 
777        day: '2-digit', 
778        month: '2-digit', 
779        year: 'numeric' 
780      });
781    }
782    
783    // Show entrada calendar
784    fechaEntrada.addEventListener('click', () => {
785      if (!entradaPickerVisible) {
786        // Hide salida calendar if open
787        salidaCalendar.style.display = 'none';
788        salidaPickerVisible = false;
789        
790        // Position and show entrada calendar
791        const rect = fechaEntrada.getBoundingClientRect();
792        const formRect = formContainer.getBoundingClientRect();
793        entradaCalendar.style.top = (rect.bottom - formRect.top + 5) + 'px';
794        entradaCalendar.style.left = (rect.left - formRect.left) + 'px';
795        entradaCalendar.style.display = 'block';
796        entradaPickerVisible = true;
797        
798        // Generate calendar UI
799        generateCalendarUI(new Date(), entradaCalendar, (selectedDate) => {
800          fechaEntrada.value = formatDate(selectedDate);
801          entradaCalendar.style.display = 'none';
802          entradaPickerVisible = false;
803          
804          // If salida date is before entrada date, update it
805          if (fechaSalida.value) {
806            const entradaDate = new Date(selectedDate);
807            const salidaDate = parseDate(fechaSalida.value);
808            
809            if (salidaDate < entradaDate) {
810              // Set salida date to entrada date + 1 day
811              const newSalidaDate = new Date(entradaDate);
812              newSalidaDate.setDate(newSalidaDate.getDate() + 1);
813              fechaSalida.value = formatDate(newSalidaDate);
814            }
815          }
816        });
817      } else {
818        entradaCalendar.style.display = 'none';
819        entradaPickerVisible = false;
820      }
821    });
822    
823    // Show salida calendar
824    fechaSalida.addEventListener('click', () => {
825      if (!salidaPickerVisible) {
826        // Hide entrada calendar if open
827        entradaCalendar.style.display = 'none';
828        entradaPickerVisible = false;
829        
830        // Position and show salida calendar
831        const rect = fechaSalida.getBoundingClientRect();
832        const formRect = formContainer.getBoundingClientRect();
833        salidaCalendar.style.top = (rect.bottom - formRect.top + 5) + 'px';
834        salidaCalendar.style.left = (rect.left - formRect.left) + 'px';
835        salidaCalendar.style.display = 'block';
836        salidaPickerVisible = true;
837        
838        // Generate calendar UI
839        generateCalendarUI(new Date(), salidaCalendar, (selectedDate) => {
840          fechaSalida.value = formatDate(selectedDate);
841          salidaCalendar.style.display = 'none';
842          salidaPickerVisible = false;
843          
844          // If entrada date is after salida date, update it
845          if (fechaEntrada.value) {
846            const entradaDate = parseDate(fechaEntrada.value);
847            const salidaDate = new Date(selectedDate);
848            
849            if (entradaDate > salidaDate) {
850              // Set entrada date to salida date - 1 day
851              const newEntradaDate = new Date(salidaDate);
852              newEntradaDate.setDate(newEntradaDate.getDate() - 1);
853              fechaEntrada.value = formatDate(newEntradaDate);
854            }
855          }
856        });
857      } else {
858        salidaCalendar.style.display = 'none';
859        salidaPickerVisible = false;
860      }
861    });
862    
863    // Helper function to parse date string
864    function parseDate(dateString) {
865      // Parse Spanish format date (DD/MM/YYYY)
866      const parts = dateString.split('/');
867      if (parts.length === 3) {
868        const day = parseInt(parts[0], 10);
869        const month = parseInt(parts[1], 10) - 1; // Months are 0-indexed
870        const year = parseInt(parts[2], 10);
871        return new Date(year, month, day);
872      }
873      return new Date(); // Return current date if unable to parse
874    }
875    
876    // Close calendars when clicking outside
877    document.addEventListener('click', (event) => {
878      if (entradaPickerVisible && !entradaCalendar.contains(event.target) && event.target !== fechaEntrada) {
879        entradaCalendar.style.display = 'none';
880        entradaPickerVisible = false;
881      }
882      
883      if (salidaPickerVisible && !salidaCalendar.contains(event.target) && event.target !== fechaSalida) {
884        salidaCalendar.style.display = 'none';
885        salidaPickerVisible = false;
886      }
887    });
888  }
889
890  // =============================
891  // Skiday Single Date Picker Functionality
892  // =============================
893  if (fechaReserva) {
894    let reservaPickerVisible = false;
895    
896    // Create calendar element
897    const reservaCalendar = document.createElement('div');
898    reservaCalendar.className = 'date-calendar reserva-calendar';
899    reservaCalendar.style.display = 'none';
900    reservaCalendar.style.position = 'absolute';
901    reservaCalendar.style.zIndex = '1000';
902    reservaCalendar.style.backgroundColor = '#fff';
903    reservaCalendar.style.boxShadow = '0 4px 12px rgba(0,0,0,0.1)';
904    reservaCalendar.style.borderRadius = '8px';
905    reservaCalendar.style.padding = '16px';
906    reservaCalendar.style.width = '280px';
907    
908    // Append calendar to form container
909    const skidayFormContainer = document.querySelector('.skiday-reservation__form-container');
910    if (skidayFormContainer) {
911      skidayFormContainer.appendChild(reservaCalendar);
912    }
913    
914    // Helper to create a simple calendar UI (reusing the same function from alojamiento)
915    function generateSkidayCalendarUI(date, calendarElement, onSelectCallback) {
916      // Clear previous content
917      calendarElement.innerHTML = '';
918      
919      const year = date.getFullYear();
920      const month = date.getMonth();
921      
922      // Create calendar header
923      const header = document.createElement('div');
924      header.className = 'calendar-header';
925      header.style.display = 'flex';
926      header.style.justifyContent = 'space-between';
927      header.style.marginBottom = '12px';
928      
929      const prevMonth = document.createElement('button');
930      prevMonth.innerHTML = '&lt;';
931      prevMonth.style.border = 'none';
932      prevMonth.style.background = 'none';
933      prevMonth.style.fontSize = '16px';
934      prevMonth.style.cursor = 'pointer';
935      
936      const monthTitle = document.createElement('div');
937      monthTitle.textContent = new Date(year, month, 1).toLocaleDateString('es-ES', { month: 'long', year: 'numeric' });
938      monthTitle.style.fontSize = '16px';
939      monthTitle.style.fontWeight = 'bold';
940      
941      const nextMonth = document.createElement('button');
942      nextMonth.innerHTML = '&gt;';
943      nextMonth.style.border = 'none';
944      nextMonth.style.background = 'none';
945      nextMonth.style.fontSize = '16px';
946      nextMonth.style.cursor = 'pointer';
947      
948      header.appendChild(prevMonth);
949      header.appendChild(monthTitle);
950      header.appendChild(nextMonth);
951      
952      // Create calendar grid
953      const grid = document.createElement('div');
954      grid.className = 'calendar-grid';
955      grid.style.display = 'grid';
956      grid.style.gridTemplateColumns = 'repeat(7, 1fr)';
957      grid.style.gap = '8px';
958      
959      // Days of week
960      const daysOfWeek = ['D', 'L', 'M', 'X', 'J', 'V', 'S'];
961      daysOfWeek.forEach(day => {
962        const dayElem = document.createElement('div');
963        dayElem.textContent = day;
964        dayElem.style.textAlign = 'center';
965        dayElem.style.fontSize = '14px';
966        dayElem.style.fontWeight = 'bold';
967        grid.appendChild(dayElem);
968      });
969      
970      // Calculate first day of month and number of days
971      const firstDay = new Date(year, month, 1).getDay();
972      const daysInMonth = new Date(year, month + 1, 0).getDate();
973      
974      // Add empty cells for days before first of month
975      for (let i = 0; i < firstDay; i++) {
976        const emptyDay = document.createElement('div');
977        grid.appendChild(emptyDay);
978      }
979      
980      // Add days of month
981      for (let day = 1; day <= daysInMonth; day++) {
982        const dayElem = document.createElement('div');
983        dayElem.textContent = day;
984        dayElem.style.textAlign = 'center';
985        dayElem.style.padding = '6px';
986        dayElem.style.cursor = 'pointer';
987        dayElem.style.borderRadius = '4px';
988        
989        // Highlight current date
990        const currentDate = new Date();
991        if (day === currentDate.getDate() && month === currentDate.getMonth() && year === currentDate.getFullYear()) {
992          dayElem.style.backgroundColor = '#e6f7ff';
993        }
994        
995        dayElem.addEventListener('mouseover', () => {
996          dayElem.style.backgroundColor = '#f0f0f0';
997        });
998        
999        dayElem.addEventListener('mouseout', () => {
1000          if (!(day === currentDate.getDate() && month === currentDate.getMonth() && year === currentDate.getFullYear())) {
1001            dayElem.style.backgroundColor = '';
1002          }
1003        });
1004        
1005        dayElem.addEventListener('click', () => {
1006          const selectedDate = new Date(year, month, day);
1007          onSelectCallback(selectedDate);
1008        });
1009        
1010        grid.appendChild(dayElem);
1011      }
1012      
1013      // Event listeners for month navigation
1014      prevMonth.addEventListener('click', () => {
1015        const prevMonthDate = new Date(year, month - 1, 1);
1016        generateSkidayCalendarUI(prevMonthDate, calendarElement, onSelectCallback);
1017      });
1018      
1019      nextMonth.addEventListener('click', () => {
1020        const nextMonthDate = new Date(year, month + 1, 1);
1021        generateSkidayCalendarUI(nextMonthDate, calendarElement, onSelectCallback);
1022      });
1023      
1024      calendarElement.appendChild(header);
1025      calendarElement.appendChild(grid);
1026    }
1027    
1028    // Format date for display
1029    function formatSkidayDate(date) {
1030      return date.toLocaleDateString('es-ES', { 
1031        day: '2-digit', 
1032        month: '2-digit', 
1033        year: 'numeric' 
1034      });
1035    }
1036    
1037    // Show calendar when clicking on date input
1038    fechaReserva.addEventListener('click', () => {
1039      if (!reservaPickerVisible) {
1040        // Position and show calendar
1041        const rect = fechaReserva.getBoundingClientRect();
1042        const formRect = skidayFormContainer.getBoundingClientRect();
1043        reservaCalendar.style.top = (rect.bottom - formRect.top + 5) + 'px';
1044        reservaCalendar.style.left = (rect.left - formRect.left) + 'px';
1045        reservaCalendar.style.display = 'block';
1046        reservaPickerVisible = true;
1047        
1048        // Generate calendar UI
1049        generateSkidayCalendarUI(new Date(), reservaCalendar, (selectedDate) => {
1050          fechaReserva.value = formatSkidayDate(selectedDate);
1051          reservaCalendar.style.display = 'none';
1052          reservaPickerVisible = false;
1053        });
1054      } else {
1055        reservaCalendar.style.display = 'none';
1056        reservaPickerVisible = false;
1057      }
1058    });
1059    
1060    // Close calendar when clicking outside
1061    document.addEventListener('click', (event) => {
1062      if (reservaPickerVisible && !reservaCalendar.contains(event.target) && event.target !== fechaReserva) {
1063        reservaCalendar.style.display = 'none';
1064        reservaPickerVisible = false;
1065      }
1066    });
1067  }
1068
1069  // Accordion functionality
1070  function initAccordions() {
1071    const accordions = document.querySelectorAll('.accordion');
1072    
1073    accordions.forEach(accordion => {
1074      const headers = accordion.querySelectorAll('.accordion__header');
1075      
1076      headers.forEach(header => {
1077        header.addEventListener('click', function() {
1078          const isExpanded = this.getAttribute('aria-expanded') === 'true';
1079          const targetPanel = document.getElementById(this.getAttribute('aria-controls'));
1080          
1081          // Close all panels in this accordion
1082          headers.forEach(h => {
1083            if (h !== this) {
1084              h.setAttribute('aria-expanded', 'false');
1085              const panel = document.getElementById(h.getAttribute('aria-controls'));
1086              panel.style.maxHeight = '0';
1087              panel.setAttribute('aria-hidden', 'true');
1088            }
1089          });
1090          
1091          // Toggle the clicked panel
1092          if (isExpanded) {
1093            this.setAttribute('aria-expanded', 'false');
1094            targetPanel.style.maxHeight = '0';
1095            targetPanel.setAttribute('aria-hidden', 'true');
1096          } else {
1097            this.setAttribute('aria-expanded', 'true');
1098            targetPanel.style.maxHeight = targetPanel.scrollHeight + 'px';
1099            targetPanel.setAttribute('aria-hidden', 'false');
1100          }
1101        });
1102      });
1103      
1104      // Initialize all panels to be closed
1105      headers.forEach(header => {
1106        const panel = document.getElementById(header.getAttribute('aria-controls'));
1107        panel.style.maxHeight = '0';
1108      });
1109    });
1110  }
1111
1112  // Initialize accordions when DOM is loaded
1113  initAccordions();
1114
1115  // Breadcrumb mobile toggle
1116  const breadcrumbToggle = document.querySelector('.breadcrumb__toggle');
1117  if (breadcrumbToggle) {
1118    breadcrumbToggle.addEventListener('click', function() {
1119      const breadcrumb = this.closest('.breadcrumb');
1120      if (breadcrumb) {
1121        breadcrumb.classList.toggle('is-open');
1122        const isExpanded = breadcrumb.classList.contains('is-open');
1123        this.setAttribute('aria-expanded', isExpanded);
1124      }
1125    });
1126  }
1127
1128  // Initialize newsletter form
1129  initNewsletterForm();
1130  
1131  // Initialize alojamiento form
1132  initAlojamientoForm();
1133});
1134
1135// Season Pass Benefits Modal Functions
1136let currentPassPurchaseUrl = null; // Store the current pass purchase URL
1137
1138window.showPassBenefits = function(passType) {
1139  const modal = document.getElementById('pass-benefits-modal');
1140  const modalTitle = document.getElementById('modal-title');
1141  const modalBody = document.getElementById('modal-body');
1142  
1143  if (!modal || !modalTitle || !modalBody) {
1144    console.error('Modal elements not found');
1145    return;
1146  }
1147  
1148  const contentElement = document.getElementById('modal-content-' + passType);
1149  
1150  if (!contentElement) {
1151    console.error('Modal content not found for pass type:', passType);
1152    return;
1153  }
1154  
1155  const title = contentElement.getAttribute('data-modal-title');
1156  const content = contentElement.innerHTML;
1157  
1158  // Store the purchase URL for this pass
1159  currentPassPurchaseUrl = contentElement.getAttribute('data-purchase-url');
1160  
1161  modalTitle.textContent = title;
1162  modalBody.innerHTML = content;
1163  
1164  modal.style.display = 'flex';
1165  document.body.style.overflow = 'hidden';
1166};
1167
1168window.closePassModal = function() {
1169  const modal = document.getElementById('pass-benefits-modal');
1170  if (modal) {
1171    modal.style.display = 'none';
1172    document.body.style.overflow = '';
1173  }
1174};
1175
1176window.goToPurchase = function() {
1177  // Use the stored purchase URL for the current pass
1178  let purchaseUrl = 'https://store.laparva.cl/?_gl=1*oqajxf*_ga*MjEzNTQ4NTM4Ny4xNzQ0NzMwODQ0*_ga_3D5TVFXWDN*czE3NTEzMDY3MTgkbzUkZzEkdDE3NTEzMDcwOTQkajUyJGwwJGgw'; // fallback URL
1179  
1180  if (currentPassPurchaseUrl) {
1181    purchaseUrl = currentPassPurchaseUrl;
1182  }
1183  
1184  window.open(purchaseUrl, '_blank');
1185  closePassModal();
1186};
1187
1188// Newsletter subscription functionality
1189function initNewsletterForm() {
1190  const newsletterForm = document.getElementById('newsletter-form');
1191  
1192  if (newsletterForm) {
1193    newsletterForm.addEventListener('submit', function(e) {
1194      e.preventDefault();
1195      
1196      const emailInput = document.getElementById('newsletter-email');
1197      const submitButton = document.getElementById('newsletter-submit');
1198      const messageDiv = document.getElementById('newsletter-message');
1199      const nonceInput = document.querySelector('input[name="newsletter_nonce"]');
1200      
1201      // Get form data
1202      const email = emailInput.value.trim();
1203      const nonce = nonceInput ? nonceInput.value : '';
1204      
1205      // Basic validation
1206      if (!email) {
1207        const message = (typeof laparva_ajax !== 'undefined' && laparva_ajax.messages) ? 
1208                       laparva_ajax.messages.empty_email : 'Por favor ingresa tu email.';
1209        showNewsletterMessage(message, 'error');
1210        return;
1211      }
1212      
1213      if (!isValidEmail(email)) {
1214        const message = (typeof laparva_ajax !== 'undefined' && laparva_ajax.messages) ? 
1215                       laparva_ajax.messages.invalid_email : 'Por favor ingresa un email válido.';
1216        showNewsletterMessage(message, 'error');
1217        return;
1218      }
1219      
1220      // Show loading state
1221      setNewsletterLoading(true);
1222      
1223      // Prepare data for AJAX request
1224      const formData = new FormData();
1225      formData.append('action', 'laparva_newsletter_subscribe');
1226      formData.append('email', email);
1227      formData.append('nonce', nonce);
1228      
1229      // Add language information
1230      const language = (typeof laparva_ajax !== 'undefined' && laparva_ajax.language) ? 
1231                      laparva_ajax.language : 'es_ES';
1232      formData.append('language', language);
1233      
1234      // Make AJAX request
1235      const ajaxUrl = (typeof laparva_ajax !== 'undefined') ? laparva_ajax.ajax_url : '/wp-admin/admin-ajax.php';
1236      fetch(ajaxUrl, {
1237        method: 'POST',
1238        body: formData
1239      })
1240      .then(response => response.json())
1241      .then(data => {
1242        setNewsletterLoading(false);
1243        
1244        if (data.success) {
1245          showNewsletterMessage(data.data.message, 'success');
1246          emailInput.value = ''; // Clear the form
1247        } else {
1248          showNewsletterMessage(data.data.message, 'error');
1249        }
1250      })
1251      .catch(error => {
1252        setNewsletterLoading(false);
1253        console.error('Newsletter subscription error:', error);
1254        const message = (typeof laparva_ajax !== 'undefined' && laparva_ajax.messages) ? 
1255                       laparva_ajax.messages.connection_error : 'Error de conexión. Por favor intenta nuevamente.';
1256        showNewsletterMessage(message, 'error');
1257      });
1258    });
1259  }
1260  
1261  function isValidEmail(email) {
1262    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1263    return emailRegex.test(email);
1264  }
1265  
1266  function setNewsletterLoading(isLoading) {
1267    const submitButton = document.getElementById('newsletter-submit');
1268    const emailInput = document.getElementById('newsletter-email');
1269    
1270    if (isLoading) {
1271      submitButton.classList.add('loading');
1272      submitButton.disabled = true;
1273      emailInput.disabled = true;
1274    } else {
1275      submitButton.classList.remove('loading');
1276      submitButton.disabled = false;
1277      emailInput.disabled = false;
1278    }
1279  }
1280  
1281  function showNewsletterMessage(message, type) {
1282    const messageDiv = document.getElementById('newsletter-message');
1283    
1284    messageDiv.className = 'newsletter-message ' + type;
1285    messageDiv.textContent = message;
1286    messageDiv.style.display = 'block';
1287    
1288    // Auto-hide success messages after 5 seconds
1289    if (type === 'success') {
1290      setTimeout(() => {
1291        messageDiv.style.display = 'none';
1292      }, 5000);
1293    }
1294  }
1295}
1296
1297// Alojamiento form submission functionality
1298function initAlojamientoForm() {
1299  const alojamientoForm = document.getElementById('alojamiento-form');
1300  
1301  if (alojamientoForm) {
1302    alojamientoForm.addEventListener('submit', function(e) {
1303      e.preventDefault();
1304      
1305      const submitButton = document.getElementById('alojamiento-submit');
1306      const messageDiv = document.getElementById('alojamiento-message');
1307      const formsparkId = alojamientoForm.getAttribute('data-formspark-id');
1308      
1309      // Check if Formspark ID is configured
1310      if (!formsparkId || formsparkId.trim() === '') {
1311        showAlojamientoMessage('Error: Form ID not configured. Please contact the administrator.', 'error');
1312        return;
1313      }
1314      
1315      // Get form data
1316      const formData = new FormData(alojamientoForm);
1317      
1318      // Basic validation
1319      const requiredFields = ['nombre', 'email', 'telefono', 'alojamiento', 'cantidad-personas', 'fecha-entrada', 'fecha-salida'];
1320      let hasErrors = false;
1321      
1322      for (const field of requiredFields) {
1323        const value = formData.get(field);
1324        if (!value || value.trim() === '') {
1325          showAlojamientoMessage(`Por favor complete el campo: ${getFieldLabel(field)}`, 'error');
1326          hasErrors = true;
1327          break;
1328        }
1329      }
1330      
1331      if (hasErrors) return;
1332      
1333      // Validate email format
1334      const email = formData.get('email');
1335      if (!isValidEmail(email)) {
1336        showAlojamientoMessage('Por favor ingresa un email válido.', 'error');
1337        return;
1338      }
1339      
1340      // Validate dates
1341      const fechaEntrada = formData.get('fecha-entrada');
1342      const fechaSalida = formData.get('fecha-salida');
1343      
1344      if (!validateDates(fechaEntrada, fechaSalida)) {
1345        showAlojamientoMessage('La fecha de salida debe ser posterior a la fecha de entrada.', 'error');
1346        return;
1347      }
1348      
1349      // Show loading state
1350      setAlojamientoLoading(true);
1351      
1352      // Prepare data for Formspark
1353      const submissionData = {
1354        nombre: formData.get('nombre'),
1355        email: formData.get('email'),
1356        telefono: formData.get('telefono'),
1357        alojamiento: formData.get('alojamiento'),
1358        'cantidad-personas': formData.get('cantidad-personas'),
1359        'fecha-entrada': formData.get('fecha-entrada'),
1360        'fecha-salida': formData.get('fecha-salida'),
1361        timestamp: new Date().toISOString(),
1362        source: 'La Parva - Alojamiento Form'
1363      };
1364      
1365      // Submit to Formspark
1366      fetch(`https://submit-form.com/${formsparkId}`, {
1367        method: 'POST',
1368        headers: {
1369          'Content-Type': 'application/json',
1370          'Accept': 'application/json'
1371        },
1372        body: JSON.stringify(submissionData)
1373      })
1374      .then(response => {
1375        setAlojamientoLoading(false);
1376        
1377        if (response.ok) {
1378          showAlojamientoMessage('¡Gracias! Tu solicitud de reserva ha sido enviada exitosamente. Te contactaremos pronto.', 'success');
1379          alojamientoForm.reset(); // Clear the form
1380        } else {
1381          throw new Error('Form submission failed');
1382        }
1383      })
1384      .catch(error => {
1385        setAlojamientoLoading(false);
1386        console.error('Alojamiento form submission error:', error);
1387        showAlojamientoMessage('Error al enviar la solicitud. Por favor intenta nuevamente o contacta directamente.', 'error');
1388      });
1389    });
1390  }
1391  
1392  function isValidEmail(email) {
1393    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1394    return emailRegex.test(email);
1395  }
1396  
1397  function validateDates(fechaEntrada, fechaSalida) {
1398    if (!fechaEntrada || !fechaSalida) return false;
1399    
1400    // Parse dates (assuming DD/MM/YYYY format)
1401    const parseDate = (dateString) => {
1402      const parts = dateString.split('/');
1403      if (parts.length === 3) {
1404        const day = parseInt(parts[0], 10);
1405        const month = parseInt(parts[1], 10) - 1; // Months are 0-indexed
1406        const year = parseInt(parts[2], 10);
1407        return new Date(year, month, day);
1408      }
1409      return null;
1410    };
1411    
1412    const entradaDate = parseDate(fechaEntrada);
1413    const salidaDate = parseDate(fechaSalida);
1414    
1415    if (!entradaDate || !salidaDate) return false;
1416    
1417    return salidaDate > entradaDate;
1418  }
1419  
1420  function getFieldLabel(fieldName) {
1421    const labels = {
1422      'nombre': 'Nombre',
1423      'email': 'Email',
1424      'telefono': 'Teléfono',
1425      'alojamiento': 'Alojamiento',
1426      'cantidad-personas': 'Cantidad de personas',
1427      'fecha-entrada': 'Fecha de entrada',
1428      'fecha-salida': 'Fecha de salida'
1429    };
1430    return labels[fieldName] || fieldName;
1431  }
1432  
1433  function setAlojamientoLoading(isLoading) {
1434    const submitButton = document.getElementById('alojamiento-submit');
1435    const formInputs = document.querySelectorAll('#alojamiento-form input, #alojamiento-form select');
1436    
1437    if (isLoading) {
1438      submitButton.classList.add('loading');
1439      submitButton.disabled = true;
1440      formInputs.forEach(input => input.disabled = true);
1441    } else {
1442      submitButton.classList.remove('loading');
1443      submitButton.disabled = false;
1444      formInputs.forEach(input => input.disabled = false);
1445    }
1446  }
1447  
1448  function showAlojamientoMessage(message, type) {
1449    const messageDiv = document.getElementById('alojamiento-message');
1450    
1451    messageDiv.className = 'alojamiento-message ' + type;
1452    messageDiv.textContent = message;
1453    messageDiv.style.display = 'block';
1454    
1455    // Auto-hide success messages after 8 seconds
1456    if (type === 'success') {
1457      setTimeout(() => {
1458        messageDiv.style.display = 'none';
1459      }, 8000);
1460    }
1461    
1462    // Scroll message into view
1463    messageDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
1464  }
1465}
1466
1467

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.