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 = '<'; 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 = '>'; 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 = '<'; 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 = '>'; 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.