1/** 2 * HOME.JS - Scripts para la nueva Landing de AvaiBook 3 * Versión: 3.0 4 * Versión landing home: 22.7 5 * Autor: Equipo AvaiBook 6 * Fecha: noviembre 2025 7 * Ãltima actualización: 2025-11-26 [REFACTOR: Detección de idioma simplificada] 8 * Descripción: Archivo de animaciones e interactividad para la landing home. 9 * 10 * CAMBIOS v3.0: 11 * - Carrusel de beneficios ahora recibe idioma SOLO desde evento avaibook:languageReady de script.js 12 * - Eliminado localStorage completamente 13 * - Eliminado fallback a localStorage (solo español si el evento no se recibe) 14 * - Sistema de detección de idioma más limpio y centralizado 15 * 16 * Funcionalidades incluidas: 17 * 1. Animaciones al scroll - Elementos principales (fade-in) 18 * 2. Smooth scroll para enlaces internos 19 * 3. Animación de beneficios clave (AB-reveal) 20 * 4. Animación de tarjetas escalonadas (delayed card animations) 21 * 5. Efecto flip card para tarjetas coloridas 22 * 6. Animación de logos de partners con delays incrementales 23 * 7. Carrusel de beneficios multiidioma (ES/PT/IT) - Idioma desde evento avaibook:languageReady 24 */ 25 26// LOG INMEDIATO - Se ejecuta apenas se carga el archivo 27console.log('ð [HOME.JS v3.0] Archivo cargado - Detección de idioma simplificada'); 28console.log('ð [HOME.JS] URL:', window.location.pathname); 29 30 31document.addEventListener('DOMContentLoaded', function () { 32 console.log('â [HOME.JS] DOMContentLoaded disparado - Inicializando...'); 33 34 // ================================================== 35 // 1. ANIMACIONES AL SCROLL - Elementos principales 36 // ================================================== 37 function initScrollAnimations() { 38 const observerOptions = { 39 threshold: 0.25, 40 rootMargin: '0px 0px -50px 0px' 41 }; 42 43 const observer = new IntersectionObserver(function (entries) { 44 entries.forEach(entry => { 45 if (entry.isIntersecting) { 46 entry.target.classList.add('AB-visible'); 47 } 48 }); 49 }, observerOptions); 50 51 // Aplicar animaciones a las tarjetas de productos y elementos 52 const fadeElements = document.querySelectorAll('.AB-fade-in'); 53 fadeElements.forEach(element => { 54 observer.observe(element); 55 }); 56 } 57 58 // ========================================== 59 // 2. SMOOTH SCROLL PARA ENLACES INTERNOS 60 // ========================================== 61 function initSmoothScroll() { 62 document.querySelectorAll('a[href^="#"]').forEach(anchor => { 63 anchor.addEventListener('click', function (e) { 64 e.preventDefault(); 65 const target = document.querySelector(this.getAttribute('href')); 66 if (target) { 67 target.scrollIntoView({ 68 behavior: 'smooth', 69 block: 'start' 70 }); 71 } 72 }); 73 }); 74 } 75 76 // ========================================== 77 // 3. ANIMACIÃN DE BENEFICIOS CLAVE (AB-reveal) 78 // ========================================== 79 function initRevealAnimations() { 80 const revealElements = document.querySelectorAll('.AB-reveal'); 81 82 if ("IntersectionObserver" in window) { 83 const observer = new IntersectionObserver((entries, observer) => { 84 entries.forEach(entry => { 85 if (entry.isIntersecting) { 86 entry.target.classList.add('visible'); 87 // Opcional: dejar de observar el elemento una vez que es visible 88 // observer.unobserve(entry.target); 89 } 90 }); 91 }, { 92 threshold: 0.1 // El elemento se revela cuando el 10% es visible 93 }); 94 95 revealElements.forEach(el => { 96 observer.observe(el); 97 }); 98 } else { 99 // Fallback para navegadores antiguos sin IntersectionObserver 100 revealElements.forEach(el => el.classList.add('visible')); 101 } 102 } 103 104 105 106 // ========================================== 107 // 4. ANIMACIÃN DE TARJETAS ESCALONADAS 108 // ========================================== 109 function initCardAnimations() { 110 const animatedCards = document.querySelectorAll('.AB-animated-card'); 111 112 const observerOptions = { 113 root: null, 114 rootMargin: '0px', 115 threshold: 0.2 // El 20% del elemento debe ser visible 116 }; 117 118 const cardObserver = new IntersectionObserver((entries, observer) => {
119 entries.forEach((entry, index) => { 120 if (entry.isIntersecting) { 121 // Retraso de 600ms entre cada carta 122 setTimeout(() => { 123 entry.target.classList.add('is-in-view'); 124 }, index * 600); 125 126 // OPTIMIZACIÃN: Dejamos de observar este elemento una vez animado 127 observer.unobserve(entry.target); 128 } 129 }); 130 }, observerOptions); 131 132 animatedCards.forEach(card => { 133 cardObserver.observe(card); 134 }); 135 } 136 137 // ========================================== 138 // 5. EFECTO FLIP CARD 139 // ========================================== 140 function initFlipCards() { 141 const flipCards = document.querySelectorAll('.AB-colorful-item'); 142 let currentFlipped = null; 143 144 flipCards.forEach(card => { 145 card.addEventListener('click', function () { 146 // Si hay una carta volteada y no es la actual, la regresamos 147 if (currentFlipped && currentFlipped !== this) { 148 currentFlipped.classList.remove('flipped'); 149 } 150 151 // Alternar el estado de la carta actual 152 this.classList.toggle('flipped'); 153 154 // Actualizar la referencia de la carta volteada 155 if (this.classList.contains('flipped')) { 156 currentFlipped = this; 157 } else { 158 currentFlipped = null; 159 } 160 }); 161 }); 162 } 163 164 // ========================================== 165 // 6. ANIMACIÃN DE LOGOS DE PARTNERS 166 // ========================================== 167 function initLogoAnimations() { 168 const logos = document.querySelectorAll('.AB-home-logo-item'); 169 170 171 if (logos.length === 0) { 172 console.warn('â ï¸ No se encontraron elementos .AB-home-logo-item'); 173 return; 174 } 175 176 let delay = 0; 177 178 // Asigna delays incrementales para aparecer uno por uno 179 logos.forEach((logo, index) => { 180 logo.style.transitionDelay = delay + 's'; 181 delay += 0.2; // Ajusta el retraso entre apariciones (0.2s por logo) 182 183 }); 184 185 // Función para verificar si están en viewport y activar 186 function checkIfInView() { 187 logos.forEach((logo, index) => { 188 const rect = logo.getBoundingClientRect(); 189 // Aparecen cuando están al 90% del viewport (más tarde en el scroll) 190 const inView = rect.top <= (window.innerHeight * 0.9) && rect.bottom >= 0; 191 192 if (inView) { 193 logo.classList.add('AB-visible'); 194 } 195 }); 196 } 197 198 // Inicializa al cargar y al scroll 199 200 checkIfInView(); 201 window.addEventListener('scroll', checkIfInView); 202 } 203 204 // ========================================== 205 // 7. CARRUSEL DE BENEFICIOS 206 // ========================================== 207 208 function initCarruselBeneficios(forcedLanguage) { 209 // ========================================== 210 // SISTEMA DE DETECCIÃN DE IDIOMA (v3.0 - Simplificado) 211 // ========================================== 212 // El idioma se recibe desde evento avaibook:languageReady de script.js 213 // Si no se recibe el evento (race condition), fallback a español 214 215 // PREVENIR DOBLE INICIALIZACIÃN 216 if (window.avaiBookCarruselInitialized) { 217 console.log('ð [HOME.JS] Carrusel ya inicializado, saltando...'); 218 return; 219 } 220 221 // Función auxiliar: obtener idioma desde parámetro del evento 222 function detectLanguage() { 223 /* const lang = forcedLanguage || 'es'; 224 console.log(`ð [HOME.JS] Idioma para carrusel: ${lang.toUpperCase()} ${forcedLanguage ? '(desde evento avaibook:languageReady)' : '(fallback a español)'}`); 225 return lang; */ 226 227 228 const lang = localStorage.getItem("userLang"); 229 console.log(`ð [HOME.JS] Idioma para carrusel: ${lang.toUpperCase()} ${forcedLanguage ? '(desde evento avaibook:languageReady)' : '(fallback a español)'}`); 230 return lang; 231 } 232 233 const currentLanguage = detectLanguage(); 234 console.log(`ð [HOME.JS v3.0] Inicializando carrusel en idioma: ${currentLanguage.toUpperCase()}`); 235 236 // ========================================== 237 // DATOS MULTIIDIOMA DEL CARRUSEL 238 // ========================================== 239 240 const CAROUSEL_DATA = { 241 es: [ 242 { 243 imagen: "https://www.avaibook.com/wp-content/uploads/2021/02/configuracion-rapida.png", 244 titulo: "Configuración rápida y gratuita", 245 texto: "Sincroniza o crea anuncios de forma rápida y sencilla. Optimiza su visibilidad gracias a nuestro onboarding ", 246 enlace: "#configuracion" 247 }, 248 { 249 imagen: "https://www.avaibook.com/wp-content/uploads/2021/02/formaciones-avaibook-1.png", 250 titulo: "Formaciones y recursos online", 251 texto: "Compartimos contigo nuestra experiencia con sesiones que contemplan cambios en normativa, integraciones, nuevas funcionalidadesâ¦", 252 enlace: "#formaciones" 253 }, 254 { 255 imagen: "https://www.avaibook.com/wp-content/uploads/2021/02/producto-adaptado.png", 256 titulo: "Producto adaptado a ti", 257 texto: "AvaiBook está pensado por y para propietarios y gestores de alquileres turÃsticos, ¡cubrimos todas tus necesidades!", 258 enlace: "#personalizacion" 259 }, 260 { 261 imagen: "https://www.avaibook.com/wp-content/uploads/2021/02/integraciones-partners.png", 262 titulo: "Integraciones con partners clave del sector", 263 texto: "Conéctate con los proveedores lÃderes del sector y complementa todavÃa más el rendimiento de tus alojamientos", 264 enlace: "#integraciones" 265 } 266 ], 267 pt: [ 268 { 269 imagen: "https://www.avaibook.com/wp-content/uploads/2021/02/configuracion-rapida.png", 270 titulo: "Configuração de conta rápida e gratuita", 271 texto: "Cria e sincroniza anúncios facilmente. Melhora a visibilidade do teu alojamento com o nosso onboarding personalizado e gratuito.", 272 enlace: "#configuracion" 273 }, 274 { 275 imagen: "https://www.avaibook.com/wp-content/uploads/2021/02/formaciones-avaibook-1.png", 276 titulo: "Formação online e conteúdos exclusivos", 277 texto: "Fica a par das atualizações sobre legislação nacional, integrações e novidades através de sessões de formação online e gratuitas.", 278 enlace: "#formaciones" 279 }, 280 { 281 imagen: "https://www.avaibook.com/wp-content/uploads/2021/02/producto-adaptado.png", 282 titulo: "Solução pensada para ti", 283 texto: "O software AvaiBook by idealista foi criado especialmente para o alojamento local e feito a pensar nas necessidades de proprietários e gestores.", 284 enlace: "#personalizacion" 285 }, 286 { 287 imagen: "https://www.avaibook.com/wp-content/uploads/2021/02/integraciones-partners.png", 288 titulo: "Integrações com os principais parceiros do setor", 289 texto: "Integramos com os fornecedores lÃderes do mercado para complementar ainda mais o desempenho dos teus alojamentos.", 290 enlace: "#integraciones" 291 } 292 ], 293 it: [ 294 { 295 imagen: "https://www.avaibook.com/wp-content/uploads/2021/02/configuracion-rapida.png", 296 titulo: "Configurazione rapida e gratuita", 297 texto: "Sincronizza o crea annunci in modo rapido e semplice. Ottimizza la loro visibilità grazie al nostro on-boarding", 298 enlace: "#configuracion" 299 }, 300 { 301 imagen: "https://www.avaibook.com/wp-content/uploads/2021/02/formaciones-avaibook-1.png", 302 titulo: "Formazioni e risorse online", 303 texto: "Condividiamo con te la nostra esperienza con sessioni in cui parliamo di cambi normativi, integrazioni, nuove funzionalità â¦", 304 enlace: "#formaciones" 305 }, 306 { 307 imagen: "https://www.avaibook.com/wp-content/uploads/2021/02/producto-adaptado.png", 308 titulo: "Prodotto adattato a te", 309 texto: "AvaiBook è pensato da e per proprietari e gestori di affitti turistici, copriamo tutte le tue esigenze!", 310 enlace: "#personalizacion" 311 }, 312 { 313 imagen: "https://www.avaibook.com/wp-content/uploads/2021/02/integraciones-partners.png", 314 titulo: "Integrazioni con partner chiave del settore", 315 texto: "Connettiti con i fornitori leader del settore e migliora ancora di più le performance dei tuoi alloggi", 316 enlace: "#integraciones" 317 } 318 ] 319 }; 320 321 // Obtener datos del idioma actual (con fallback a español) 322 const contenidoData = CAROUSEL_DATA[currentLanguage] || CAROUSEL_DATA['es']; 323 324 if (!CAROUSEL_DATA[currentLanguage]) { 325 console.warn(`â ï¸ [HOME.JS] No hay datos para idioma "${currentLanguage}", usando español`); 326 } 327 328 console.log(`ð [HOME.JS] Contenido del carrusel:`, contenidoData.length, 'elementos en', currentLanguage.toUpperCase()); 329 330 // Configuración del carrusel 331 const carrusel = document.getElementById('carrusel'); 332 const prevBtn = document.getElementById('prevBtn'); 333 const nextBtn = document.getElementById('nextBtn'); 334 const indicators = document.querySelectorAll('.AB-home-indicator'); 335 const totalItems = contenidoData.length; 336 let currentIndex = 0; 337 338 if (!carrusel) { 339 console.error('â [HOME.JS] No se encontró el elemento #carrusel'); 340 return; 341 } 342 343 // Obtener las tarjetas por posición (solo 3 posiciones) 344 const cards = { 345 'left': carrusel.querySelector('[data-position="left"]'), 346 'center': carrusel.querySelector('[data-position="center"]'), 347 'right': carrusel.querySelector('[data-position="right"]') 348 }; 349 350 // Obtener contenedores de posiciones 351 const leftPosition = carrusel.querySelector('.AB-home-left-position'); 352 const rightPosition = carrusel.querySelector('.AB-home-right-position'); 353 354 // Función para actualizar el contenido de una tarjeta 355 function updateCard(card, data) { 356 if (!card || !data) return; 357 358 const img = card.querySelector('.AB-home-card-image'); 359 const title = card.querySelector('.AB-home-card-title'); 360 const text = card.querySelector('.AB-home-card-text'); 361 362 img.src = data.imagen; 363 img.alt = data.titulo; 364 title.textContent = data.titulo; 365 if (text) text.textContent = data.texto; 366 } 367 368 // Handlers separados para evitar problemas de closure 369 function handleLeftCardClick(e) { 370 e.preventDefault(); 371 e.stopPropagation(); 372 if (currentIndex > 0) { 373 currentIndex--; 374 updateCarrusel(); 375 } 376 } 377 378 function handleRightCardClick(e) { 379 e.preventDefault(); 380 e.stopPropagation(); 381 if (currentIndex < totalItems - 1) { 382 currentIndex++; 383 updateCarrusel(); 384 } 385 } 386 387 // Función para re-establecer event listeners después de cada actualización 388 function attachCardListeners() { 389 // Obtener elementos actuales 390 const leftCardElement = carrusel.querySelector('[data-position="left"]'); 391 const rightCardElement = carrusel.querySelector('[data-position="right"]'); 392 393 if (leftCardElement && !leftCardElement.classList.contains('AB-home-card-hidden')) { 394 leftCardElement.removeEventListener('click', handleLeftCardClick); 395 leftCardElement.addEventListener('click', handleLeftCardClick); 396 } 397 398 if (rightCardElement && !rightCardElement.classList.contains('AB-home-card-hidden')) { 399 rightCardElement.removeEventListener('click', handleRightCardClick); 400 rightCardElement.addEventListener('click', handleRightCardClick); 401 } 402 } 403 404 // Función para actualizar el carrusel 405 function updateCarrusel() { 406 const isMobile = window.innerWidth <= 768; 407 408 if (isMobile) { 409 // En móvil, solo mostrar la tarjeta central 410 const centerCard = cards['center']; 411 updateCard(centerCard, contenidoData[currentIndex]); 412 413 // Ocultar posiciones laterales 414 leftPosition.style.display = 'none'; 415 rightPosition.style.display = 'none'; 416 } else { 417 // Mostrar posiciones laterales 418 leftPosition.style.display = 'flex'; 419 rightPosition.style.display = 'flex'; 420 421 // La tarjeta central SIEMPRE está fija y muestra el contenido actual 422 const centerCard = cards['center']; 423 updateCard(centerCard, contenidoData[currentIndex]); 424 425 // Posición izquierda: muestra la tarjeta anterior (si existe) 426 const leftCard = cards['left']; 427 const leftIndex = currentIndex - 1; 428 429 leftCard.classList.remove('AB-home-card-featured', 'AB-home-card-side', 'AB-home-card-hidden'); 430 431 if (leftIndex < 0) { 432 // No hay tarjeta anterior, ocultar 433 leftCard.classList.add('AB-home-card-hidden'); 434 } else { 435 // Mostrar tarjeta anterior 436 leftCard.classList.add('AB-home-card-side'); 437 updateCard(leftCard, contenidoData[leftIndex]); 438 } 439 440 // Posición derecha: muestra la tarjeta siguiente (si existe) 441 const rightCard = cards['right']; 442 const rightIndex = currentIndex + 1; 443 444 rightCard.classList.remove('AB-home-card-featured', 'AB-home-card-side', 'AB-home-card-hidden'); 445 446 if (rightIndex >= totalItems) { 447 // No hay tarjeta siguiente, ocultar 448 rightCard.classList.add('AB-home-card-hidden'); 449 } else { 450 // Mostrar tarjeta siguiente 451 rightCard.classList.add('AB-home-card-side'); 452 updateCard(rightCard, contenidoData[rightIndex]); 453 } 454 } 455 456 // Actualizar indicadores
457 indicators.forEach((indicator, index) => { 458 indicator.classList.toggle('active', index === currentIndex); 459 }); 460 461 // Actualizar estado de los botones 462 prevBtn.disabled = currentIndex === 0; 463 nextBtn.disabled = currentIndex === totalItems - 1; 464 465 // Re-establecer listeners de tarjetas laterales 466 setTimeout(attachCardListeners, 100); 467 } 468 469 // Event listeners para botones de navegación 470 if (nextBtn) { 471 nextBtn.addEventListener('click', function () { 472 if (currentIndex < totalItems - 1) { 473 currentIndex++; 474 updateCarrusel(); 475 } 476 }); 477 } 478 479 if (prevBtn) { 480 prevBtn.addEventListener('click', function () { 481 if (currentIndex > 0) { 482 currentIndex--; 483 updateCarrusel(); 484 } 485 }); 486 } 487 488 // Event listeners para indicadores 489 indicators.forEach((indicator, index) => { 490 indicator.addEventListener('click', function () { 491 currentIndex = index; 492 updateCarrusel(); 493 }); 494 }); 495 496 497 498 // Navegación con teclado 499 document.addEventListener('keydown', function (e) { 500 if (e.key === 'ArrowLeft' && currentIndex > 0) { 501 currentIndex--; 502 updateCarrusel(); 503 } else if (e.key === 'ArrowRight' && currentIndex < totalItems - 1) { 504 currentIndex++; 505 updateCarrusel(); 506 } 507 }); 508 509 // Actualizar en cambio de tamaño de ventana 510 window.addEventListener('resize', function () { 511 updateCarrusel(); 512 }); 513 514 // Inicializar carrusel 515 updateCarrusel(); 516 517 // Establecer listeners de tarjetas laterales 518 attachCardListeners(); 519 520 // Marcar como inicializado globalmente 521 window.avaiBookCarruselInitialized = true; 522 523 // Log de confirmación con idioma correcto 524 console.log(`â [HOME.JS] Carrusel inicializado correctamente en ${currentLanguage.toUpperCase()}`); 525 526 // Opcional: Autoplay (descomentado por defecto) 527 /* 528 let autoplayInterval = setInterval(() => { 529 if (currentIndex < totalItems - 1) { 530 currentIndex++; 531 } else { 532 currentIndex = 0; 533 } 534 updateCarrusel(); 535 }, 4000); 536 537 // Pausar autoplay al hacer hover 538 carrusel.addEventListener('mouseenter', () => clearInterval(autoplayInterval)); 539 carrusel.addEventListener('mouseleave', () => { 540 autoplayInterval = setInterval(() => { 541 if (currentIndex < totalItems - 1) { 542 currentIndex++; 543 } else { 544 currentIndex = 0; 545 } 546 updateCarrusel(); 547 }, 4000); 548 }); 549 */ 550 } 551 552 553 554 // ========================================== 555 // INICIALIZACIÃN DE TODAS LAS FUNCIONES 556 // ========================================== 557 558 // Ejecutar funciones que NO dependen del idioma 559 initScrollAnimations(); 560 initSmoothScroll(); 561 initRevealAnimations(); 562 initCardAnimations(); 563 initFlipCards(); 564 initLogoAnimations(); 565 566 // ========================================== 567 // INICIALIZACIÃN DEL CARRUSEL (v3.0 - Evento de idioma centralizado) 568 // ========================================== 569 // El carrusel espera el evento avaibook:languageReady desde script.js 570 // que contiene el idioma detectado (por URL, WPML, etc.) 571 572 // Detección de idioma y guardado en variable userLang (ORP) esto hay que centralizarlo 573 574 var detectedLang = localStorage.getItem("userLang"); 575 setTimeout(function() { 576 initCarruselBeneficios(detectedLang); 577 }, 300); 578 579 // Escuchar el evento de idioma listo desde script.js 580 /* document.addEventListener('avaibook:languageReady', function(event) { 581 //const detectedLang = event.detail.language; 582 console.log('entra'); 583 console.log(`ð¯ [HOME.JS v3.0] Evento 'avaibook:languageReady' recibido - Idioma: ${detectedLang.toUpperCase()}`); 584 initCarruselBeneficios(detectedLang); 585 console.log('Alberto arriba: ' + detectedLang); 586 }); */ 587 588 // Fallback (race condition): si el evento no se dispara en 500ms, usar español 589 /* setTimeout(function() { 590 if (!window.avaiBookCarruselInitialized) { 591 console.log('Alberto: ' + detectedLang); 592 console.warn('â ï¸ [HOME.JS v3.0] Evento no recibido en 500ms, inicializando carrusel con español (fallback)'); 593 initCarruselBeneficios(detectedLang); 594 } 595 }, 500); */ 596 597 // Log para debugging 598 console.log(`â [HOME.JS] Script cargado completamente - Esperando idioma desde script.js`); 599});
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.