1/* ========================================================================== 2 Foro Hotusa Explora â JavaScript 3 Una IIFE por componente, vanilla, con guard clause al inicio. 4 ========================================================================== */ 5 6 7/** sta-fondoOlas-fhe -- INICIO */ 8/* Fondo animado de olas de puntos, agnóstico del módulo. 9 Uso: añadir la clase .sta-fondoOlas-fhe a cualquier contenedor. 10 Opcionales, por data-* en ese mismo contenedor: 11 data-olas-color "255, 255, 255" (RGB sin alfa) · por defecto blanco. 12 Sobre módulos de fondo claro hay que pasar un color 13 oscuro, ej. "47, 86, 162", o no se ven los puntos. 14 data-olas-separacion distancia entre puntos en px · por defecto 18 15 data-olas-amplitud desplazamiento vertical de la ola en px · por defecto 4.5 */ 16(function (lista) { 17 if (!lista.length) return; 18 19 // Sin canvas o si se prefiere no animar, los bloques se quedan con su fondo plano 20 if (!document.createElement('canvas').getContext) return; 21 if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; 22 23 var numero = function (valor, porDefecto) { 24 var n = parseFloat(valor); 25 return isNaN(n) ? porDefecto : n; 26 }; 27 28 var montar = function (o) { 29 var capa = document.createElement('div'); 30 capa.className = 'sta-fondoOlas-fhe_capa'; 31 capa.setAttribute('aria-hidden', 'true'); 32 33 var canvas = document.createElement('canvas'); 34 var ctx = canvas.getContext('2d'); 35 capa.appendChild(canvas); 36 o.insertBefore(capa, o.firstChild); 37 38 var SEPARACION = Math.max(4, numero(o.getAttribute('data-olas-separacion'), 18)); 39 var AMPLITUD = numero(o.getAttribute('data-olas-amplitud'), 4.5); 40 var COLOR = o.getAttribute('data-olas-color') || '255, 255, 255'; 41 42 var RADIO = 1.3; // radio base del punto 43 var RADIO_VAR = 0.8; // cuánto crece con la cresta de la ola 44 var ALFA_MIN = 0.35; 45 var ALFA_MAX = 0.85; 46 47 var dpr = 1; 48 var ancho = 0; 49 var alto = 0; 50 var puntos = []; 51 var tiempo = 0; 52 var ultimo = 0; 53 var rafId = 0; 54 var visible = false; 55 56 // Rejilla de puntos, con una variación fija por punto para romper la uniformidad 57 var construir = function () { 58 var cols = Math.ceil(ancho / SEPARACION) + 1; 59 var filas = Math.ceil(alto / SEPARACION) + 1; 60 var offsetX = (ancho - (cols - 1) * SEPARACION) * 0.5; 61 var offsetY = (alto - (filas - 1) * SEPARACION) * 0.5; 62 63 puntos = []; 64 for (var ix = 0; ix < cols; ix++) { 65 for (var iy = 0; iy < filas; iy++) { 66 puntos.push({ 67 x: offsetX + ix * SEPARACION, 68 y: offsetY + iy * SEPARACION, 69 r: Math.random() 70 }); 71 } 72 } 73 }; 74 75 var medir = function () { 76 var w = Math.max(1, Math.round(o.offsetWidth)); 77 var h = Math.max(1, Math.round(o.offsetHeight)); 78 if (w === ancho && h === alto) return false; 79 80 ancho = w; 81 alto = h; 82 dpr = Math.min(window.devicePixelRatio || 1, 2); 83 84 canvas.width = Math.round(ancho * dpr); 85 canvas.height = Math.round(alto * dpr); 86 ctx.setTransform(dpr, 0, 0, dpr, 0, 0); 87 88 construir(); 89 return true; 90 }; 91 92 var pintar = function () { 93 ctx.clearRect(0, 0, ancho, alto); 94 95 for (var i = 0; i < puntos.length; i++) { 96 var p = puntos[i]; 97 98 // Cuatro ondas cruzadas, como en el fondo de referencia 99 var e = Math.sin(p.x * 0.010 + tiempo * 0.9) * 0.55 100 + Math.sin(p.y * 0.013 - tiempo * 0.7) * 0.45 101 + Math.sin((p.x + p.y) * 0.008 + tiempo * 1.1) * 0.40 102 + Math.sin((p.x - p.y) * 0.011 - tiempo * 1.3) * 0.30; 103 104 var h = e * 0.5 + 0.5; 105 if (h < 0) h = 0; 106 if (h > 1) h = 1; 107 108 var alfa = (ALFA_MIN + (ALFA_MAX - ALFA_MIN) * h) * (0.9 + p.r * 0.15); 109 110 ctx.beginPath(); 111 ctx.arc(p.x, p.y + e * AMPLITUD, RADIO + h * RADIO_VAR, 0, 6.2832); 112 ctx.fillStyle = 'rgba(' + COLOR + ',' + alfa.toFixed(3) + ')'; 113 ctx.fill(); 114 } 115 }; 116 117 var animar = function (ahora) { 118 rafId = window.requestAnimationFrame(animar); 119 var delta = ultimo ? (ahora - ultimo) / 1000 : 0; 120 ultimo = ahora; 121 tiempo += Math.min(delta, 0.05); 122 pintar(); 123 }; 124 125 var arrancar = function () { 126 if (rafId) return; 127 ultimo = 0; 128 rafId = window.requestAnimationFrame(animar); 129 }; 130 131 var parar = function () { 132 if (!rafId) return; 133 window.cancelAnimationFrame(rafId); 134 rafId = 0; 135 }; 136 137 medir(); 138 pintar(); 139 140 var alRedimensionar = function () { 141 if (medir() && !rafId) pintar(); 142 }; 143 144 if (window.ResizeObserver) { 145 new ResizeObserver(alRedimensionar).observe(o); 146 } else { 147 window.addEventListener('resize', alRedimensionar); 148 } 149 150 // Sólo se anima mientras el módulo está en pantalla 151 if (window.IntersectionObserver) { 152 new IntersectionObserver(function (entradas) { 153 visible = entradas[0].isIntersecting; 154 if (visible && !document.hidden) arrancar(); 155 else parar(); 156 }, { threshold: 0 }).observe(o); 157 } else { 158 visible = true; 159 arrancar(); 160 } 161 162 document.addEventListener('visibilitychange', function () { 163 if (document.hidden) parar(); 164 else if (visible) arrancar(); 165 }); 166 }; 167 168 Array.prototype.forEach.call(lista, montar); 169})(document.querySelectorAll('.sta-fondoOlas-fhe')); 170/** sta-fondoOlas-fhe -- FIN */ 171 172 173/** tpl-header-fhe -- INICIO */ 174(function (o) { 175 if (!o) return; 176 177 var abrir = o.querySelector('.fn-headerAbrir');
178 var panel = o.querySelector('#sta-header-fhe_panel'); 179 if (!abrir || !panel) return; 180 181 var cerrar = panel.querySelector('.fn-headerCerrar'); 182 var foco = 'a[href], button:not([disabled]), input, select, [tabindex]:not([tabindex="-1"])'; 183 var previo = null; 184 185 var enfocables = function () { 186 return Array.prototype.filter.call(panel.querySelectorAll(foco), function (el) { 187 return el.offsetParent !== null; 188 }); 189 }; 190 191 var abrirPanel = function () { 192 previo = document.activeElement; 193 panel.hidden = false; 194 o.classList.add('sta-abierto'); 195 document.documentElement.classList.add('sta-noScroll'); 196 abrir.setAttribute('aria-expanded', 'true'); 197 var lista = enfocables(); 198 if (lista.length) lista[0].focus(); 199 }; 200 201 var cerrarPanel = function () { 202 panel.hidden = true; 203 o.classList.remove('sta-abierto'); 204 document.documentElement.classList.remove('sta-noScroll'); 205 abrir.setAttribute('aria-expanded', 'false'); 206 if (previo && previo.focus) previo.focus(); 207 }; 208 209 abrir.addEventListener('click', abrirPanel); 210 if (cerrar) cerrar.addEventListener('click', cerrarPanel); 211 212 panel.addEventListener('keydown', function (e) { 213 if (e.key === 'Escape') { 214 cerrarPanel(); 215 return; 216 } 217 if (e.key !== 'Tab') return; 218 219 var lista = enfocables(); 220 if (!lista.length) return; 221 222 var primero = lista[0]; 223 var ultimo = lista[lista.length - 1]; 224 225 if (e.shiftKey && document.activeElement === primero) { 226 e.preventDefault(); 227 ultimo.focus(); 228 } else if (!e.shiftKey && document.activeElement === ultimo) { 229 e.preventDefault(); 230 primero.focus(); 231 } 232 }); 233 234 // Cierra el panel si se pasa a desktop con el menú abierto 235 window.addEventListener('resize', function () { 236 if (window.innerWidth >= 1024 && !panel.hidden) cerrarPanel(); 237 }); 238 239 /* Enlace de la sección activa. 240 Mismo criterio que los headers del CMS (fnActual/fnControl en 241 tpl-header2-eh_*.html): se compara location.origin + location.pathname 242 contra el href ya resuelto por el navegador, con el caso especial de "/". 243 El motor puede marcar además el <li> con sta-actual vÃa {_XT_CLASE_HEADER}; 244 esto cubre las páginas en las que ese tag no viene informado. */ 245 var urlActual = location.origin + location.pathname; 246 247 var fnControl = function (link) { 248 if (link === '/') link = location.origin + '/'; 249 return link.split('?')[0].split('#')[0] === urlActual; 250 }; 251 252 /* Coincidencia por sección. 253 El enlace del menú no apunta a un Ãndice padre sino a la edición 254 destacada (p.ej. "Ediciones Anteriores" -> /ediciones-anteriores/2026/), 255 asà que /ediciones-anteriores/2024/ es una rama HERMANA, no descendiente: 256 comparar por prefijo no sirve. Se compara el primer tramo de ruta tras la 257 raÃz del idioma ("ediciones-anteriores"), que sà es común a toda la 258 sección y cambia con el idioma (en: "previous-editions"). La raÃz del 259 idioma se deduce del logo ({_URL_INDICE}), que el motor ya resuelve. */ 260 var raiz = (function () { 261 var logo = o.querySelector('.sta-header-fhe_logo'); 262 var ruta = logo ? logo.pathname : '/'; 263 return ruta.replace(/[^/]*$/, ''); 264 })(); 265 266 var fnSeccion = function (link) { 267 var ruta = link.split('?')[0].split('#')[0].replace(location.origin, ''); 268 if (ruta.indexOf(raiz) !== 0) return ''; 269 return ruta.slice(raiz.length).split('/')[0]; 270 }; 271 272 var fnActual = function () { 273 var enlaces = o.querySelectorAll('.sta-header-fhe_enlace, .sta-header-fhe_panelNav a'); 274 var marcar = function (a) { 275 a.classList.add('sta-active'); 276 var li = a.parentNode; 277 while (li && li.nodeName !== 'LI') li = li.parentNode; 278 if (li) li.classList.add('sta-actual'); 279 }; 280 281 var candidatos = Array.prototype.filter.call(enlaces, function (a) { 282 var href = a.getAttribute('href'); 283 // Sin destino propio ("#", anclas, vacÃo): nunca es la sección actual. 284 return href && href.charAt(0) !== '#'; 285 }); 286 287 var exactos = candidatos.filter(function (a) { 288 return fnControl(a.getAttribute('href') === '/' ? '/' : a.href); 289 }); 290 291 if (exactos.length) { 292 exactos.forEach(marcar); 293 return; 294 } 295 296 // Nada coincide exactamente: se marca el enlace de la misma sección. 297 // La home (sección vacÃa) queda fuera: sólo se marca por coincidencia exacta. 298 var actual = fnSeccion(urlActual); 299 if (!actual) return; 300 301 candidatos.forEach(function (a) { 302 if (fnSeccion(a.href) === actual) marcar(a); 303 }); 304 }; 305 306 fnActual(); 307})(document.querySelector('.tpl-header-fhe')); 308/** tpl-header-fhe -- FIN */ 309 310/** scrollSeccion -- INICIO */ 311/* Salto a un módulo concreto mediante el parámetro "?scroll=<id>". 312 El CMS marca cada módulo destino con la clase ".scroll_<id>" (p.ej. 313 "scroll_62655"), asà que el ancla no es un #hash sino esa clase: el 314 navegador no puede resolverla por sà mismo y hay que posicionarla aquÃ. 315 316 Cubre los dos casos: 317 1. Carga con ?scroll= en la URL (enlace externo o entre páginas). 318 2. Click en un enlace de la propia página cuyo href lleva ?scroll=: 319 se evita la recarga y se hace el desplazamiento suave. 320 321 El header de la remaquetación es position:relative, no tapa contenido, 322 asà que no se resta ninguna altura; si en el futuro pasara a fixed o 323 sticky, fnDesplazamiento() devolverÃa su altura. */ 324(function () { 325 var raiz = document.documentElement; 326
327 /* Altura a descontar por cabeceras superpuestas. Hoy 0: sólo cuenta un 328 header realmente fijo o pegajoso, no el flujo normal. */ 329 var fnDesplazamiento = function () { 330 var header = document.querySelector('.tpl-header-fhe'); 331 if (!header) return 0; 332 var pos = getComputedStyle(header).position; 333 if (pos !== 'fixed' && pos !== 'sticky') return 0; 334 return header.getBoundingClientRect().height; 335 }; 336 337 var fnDestino = function (id) { 338 // Se acepta sólo el formato que emite el CMS, para no inyectar 339 // selectores arbitrarios desde la query string. 340 if (!id || !/^[\w-]+$/.test(id)) return null; 341 return document.querySelector('.scroll_' + id); 342 }; 343 344 var fnParametro = function (url) { 345 var busqueda = url.indexOf('?') >= 0 ? url.slice(url.indexOf('?') + 1) : ''; 346 var partes = busqueda.split('#')[0].split('&'); 347 for (var i = 0; i < partes.length; i++) { 348 var par = partes[i].split('='); 349 if (par[0] === 'scroll') return decodeURIComponent(par[1] || ''); 350 } 351 return ''; 352 }; 353 354 var fnIr = function (destino, suave) { 355 var y = window.pageYOffset + destino.getBoundingClientRect().top - fnDesplazamiento(); 356 if (y < 0) y = 0; 357 358 // scroll-behavior:smooth en el CSS del usuario o prefers-reduced-motion 359 // mandan sobre la animación; window.scrollTo con "auto" salta directo. 360 var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; 361 var modo = (suave && !reduce) ? 'smooth' : 'auto'; 362 363 if ('scrollBehavior' in raiz.style) { 364 window.scrollTo({ top: y, behavior: modo }); 365 } else { 366 window.scrollTo(0, y); 367 } 368 369 // El destino no suele ser enfocable: se le da tabindex temporal para que 370 // el foco de teclado y los lectores de pantalla sigan al salto. 371 var previo = destino.getAttribute('tabindex'); 372 if (previo === null) destino.setAttribute('tabindex', '-1'); 373 destino.focus({ preventScroll: true }); 374 if (previo === null) { 375 destino.addEventListener('blur', function quitar() { 376 destino.removeAttribute('tabindex'); 377 destino.removeEventListener('blur', quitar); 378 }); 379 } 380 }; 381 382 /* 1. Parámetro en la URL de entrada. 383 Se desactiva la restauración de scroll del navegador para que no pelee 384 con el salto, y se espera a que carguen imágenes y fuentes: sin eso el 385 offset se calcula sobre un layout que todavÃa va a crecer. */ 386 var idInicial = fnParametro(location.search); 387 var destinoInicial = fnDestino(idInicial); 388 389 if (destinoInicial) { 390 if (window.history && 'scrollRestoration' in history) { 391 history.scrollRestoration = 'manual'; 392 } 393 394 var fnSaltar = function () { 395 // Recalcula el destino: los módulos con canvas o carruseles montan 396 // después del parseo y cambian la altura de la página. 397 var destino = fnDestino(idInicial) || destinoInicial; 398 fnIr(destino, false); 399 }; 400 401 var fnArrancar = function () { 402 fnSaltar(); 403 // Segunda pasada tras el siguiente frame por si algún módulo ha 404 // terminado de montarse justo después. 405 requestAnimationFrame(function () { 406 requestAnimationFrame(fnSaltar); 407 }); 408 }; 409 410 if (document.readyState === 'complete') { 411 fnArrancar(); 412 } else { 413 window.addEventListener('load', fnArrancar); 414 } 415 } 416 417 /* 2. Enlaces de esta misma página que llevan ?scroll=. 418 Delegado en el documento para cubrir también el menú del panel móvil y 419 cualquier enlace que se inyecte más tarde. */ 420 document.addEventListener('click', function (e) { 421 var a = e.target.closest ? e.target.closest('a[href]') : null; 422 if (!a) return; 423 if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return; 424 if (a.target && a.target !== '_self') return; 425 426 var href = a.getAttribute('href'); 427 if (!href || href.indexOf('scroll=') < 0) return; 428 429 // Sólo si el enlace apunta a la página en la que ya estamos. 430 if (a.origin !== location.origin || a.pathname !== location.pathname) return; 431 432 var destino = fnDestino(fnParametro(a.search)); 433 if (!destino) return; 434 435 e.preventDefault(); 436 fnIr(destino, true); 437 438 // Se refleja el destino en la URL sin recargar, para que el enlace sea 439 // compartible y el atrás del navegador funcione. 440 if (window.history && history.pushState) { 441 history.pushState(null, '', a.href); 442 } 443 444 // Si el salto se ha lanzado desde el menú móvil, se cierra. 445 var panel = document.querySelector('.tpl-header-fhe .sta-header-fhe_panel'); 446 if (panel && !panel.hidden && panel.contains(a)) { 447 var cerrar = panel.querySelector('.fn-headerCerrar'); 448 if (cerrar) cerrar.click(); 449 } 450 }); 451})(); 452/** scrollSeccion -- FIN */ 453 454 455/** tpl-navEdiciones-fhe -- INICIO */ 456(function (o) { 457 if (!o) return; 458 459 var scroll = o.querySelector('.fn-navEdicionesScroll'); 460 var prev = o.querySelector('.fn-navEdicionesPrev'); 461 var next = o.querySelector('.fn-navEdicionesNext'); 462 if (!scroll || !prev || !next) return; 463 464 var estado = function () { 465 var sobra = scroll.scrollWidth - scroll.clientWidth;
466 prev.classList.toggle('sta-inicio', scroll.scrollLeft <= 1); 467 next.classList.toggle('sta-fin', scroll.scrollLeft >= sobra - 1); 468 }; 469 470 var mover = function (signo) { 471 scroll.scrollBy({ left: signo * Math.round(scroll.clientWidth * 0.7), behavior: 'smooth' }); 472 }; 473 474 prev.addEventListener('click', function () { mover(-1); }); 475 next.addEventListener('click', function () { mover(1); }); 476 scroll.addEventListener('scroll', estado); 477 window.addEventListener('resize', estado); 478 479 // Arrastre con el ratón. Con el dedo ya se desliza solo (overflow-x del CSS), 480 // esto es para poder hacer lo mismo pulsando y arrastrando con el puntero. 481 var arrastrando = false; 482 var movido = false; 483 var xInicio = 0; 484 var scrollInicio = 0; 485 486 // El CSS deja el scroll en smooth para que las flechas animen, pero mientras 487 // se arrastra estorba: la lista irÃa por detrás del puntero. 488 var soltar = function () { 489 if (!arrastrando) return; 490 arrastrando = false; 491 scroll.style.scrollBehavior = ''; 492 o.classList.remove('sta-arrastrando'); 493 }; 494 495 scroll.addEventListener('mousedown', function (e) { 496 // Sólo botón principal, y nada de secuestrar la selección de texto del navegador 497 if (e.button !== 0) return; 498 arrastrando = true; 499 movido = false; 500 xInicio = e.clientX; 501 scrollInicio = scroll.scrollLeft;
502 scroll.style.scrollBehavior = 'auto'; 503 o.classList.add('sta-arrastrando'); 504 e.preventDefault(); 505 }); 506 507 document.addEventListener('mousemove', function (e) { 508 if (!arrastrando) return; 509 var dx = e.clientX - xInicio; 510 // Umbral pequeño para distinguir un clic en un año de un arrastre real 511 if (Math.abs(dx) > 3) movido = true; 512 scroll.scrollLeft = scrollInicio - dx; 513 }); 514 515 document.addEventListener('mouseup', soltar); 516 scroll.addEventListener('mouseleave', soltar); 517 518 // Si se ha arrastrado, el mouseup genera igualmente un clic sobre el enlace 519 // que hubiera debajo: se anula para no navegar sin querer a otra edición. 520 scroll.addEventListener('click', function (e) { 521 if (!movido) return; 522 e.preventDefault(); 523 e.stopPropagation(); 524 movido = false; 525 }, true); 526 527 // Marca la edición activa comparando el año de la URL actual con el de cada enlace 528 var anoPagina = (location.pathname.match(/(?:19|20)\d{2}/) || [])[0]; 529 if (anoPagina) { 530 var enlaces = scroll.querySelectorAll('.sta-navEdiciones-fhe_item a'); 531 for (var i = 0; i < enlaces.length; i++) { 532 var li = enlaces[i].parentNode; 533 var texto = enlaces[i].textContent.replace(/\D/g, ''); 534 var href = (enlaces[i].getAttribute('href') || '').match(/(?:19|20)\d{2}/); 535 var anoItem = texto.length === 4 ? texto : (href ? href[0] : ''); 536 li.classList.toggle('sta-actual', anoItem === anoPagina); 537 } 538 } 539 540 // Centra la edición activa al cargar 541 var actual = scroll.querySelector('.sta-actual'); 542 if (actual) {
543 scroll.scrollLeft = actual.offsetLeft - (scroll.clientWidth - actual.offsetWidth) / 2; 544 } 545 546 estado(); 547})(document.querySelector('.tpl-navEdiciones-fhe')); 548/** tpl-navEdiciones-fhe -- FIN */ 549 550 551/** tpl-videos-fhe -- INICIO */ 552(function (o) { 553 if (!o) return; 554 555 var marco = o.querySelector('.fn-videosMarco'); 556 if (!marco) return; 557 558 var pistas = o.querySelectorAll('.fn-videosPista'); 559 560 var montar = function (youtube, wistia, segundos) { 561 var src = ''; 562 563 if (youtube) { 564 src = 'https://www.youtube-nocookie.com/embed/' + encodeURIComponent(youtube) + 565 '?autoplay=1&rel=0' + (segundos ? '&start=' + segundos : ''); 566 } else if (wistia) { 567 src = 'https://fast.wistia.net/embed/iframe/' + encodeURIComponent(wistia) + '?autoPlay=true'; 568 } else { 569 return; 570 } 571 572 var viejo = marco.querySelector('iframe'); 573 if (viejo) viejo.parentNode.removeChild(viejo); 574 575 var iframe = document.createElement('iframe'); 576 iframe.src = src; 577 iframe.title = o.querySelector('.sta-h2-fhe') ? o.querySelector('.sta-h2-fhe').textContent.trim() : 'video'; 578 iframe.setAttribute('allow', 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture'); 579 iframe.setAttribute('allowfullscreen', ''); 580 iframe.setAttribute('frameborder', '0'); 581 marco.appendChild(iframe); 582 marco.classList.add('sta-reproduciendo'); 583 }; 584 585 // Convierte "01:23:45" o "12:30" en segundos 586 var aSegundos = function (txt) { 587 if (!txt) return 0; 588 var partes = txt.trim().split(':').map(Number); 589 if (partes.some(isNaN)) return 0; 590 return partes.reduce(function (total, n) { return total * 60 + n; }, 0); 591 }; 592 593 var activar = function (item) { 594 Array.prototype.forEach.call(o.querySelectorAll('.sta-videos-fhe_item'), function (li) { 595 li.classList.toggle('sta-actual', li === item); 596 }); 597 }; 598 599 Array.prototype.forEach.call(pistas, function (pista) { 600 pista.addEventListener('click', function () { 601 var item = pista.closest('.sta-videos-fhe_item'); 602 activar(item); 603 montar(pista.dataset.youtube, pista.dataset.wistia, 0); 604 }); 605 606 var item = pista.closest('.sta-videos-fhe_item'); 607 if (!item) return; 608 609 Array.prototype.forEach.call(item.querySelectorAll('.fn-videosCapitulo'), function (cap) { 610 cap.addEventListener('click', function () { 611 activar(item); 612 montar(pista.dataset.youtube, pista.dataset.wistia, aSegundos(cap.dataset.minuto)); 613 }); 614 }); 615 }); 616 617 // El botón grande reproduce la primera pista 618 var play = o.querySelector('.fn-videosPlay'); 619 if (play && pistas.length) { 620 play.addEventListener('click', function () { pistas[0].click(); }); 621 } 622})(document.querySelector('.tpl-videos-fhe')); 623/** tpl-videos-fhe -- FIN */ 624 625 626/** tpl-galeria-fhe -- INICIO */ 627(function (o, datos) { 628 if (!o) return; 629 630 var mosaico = o.querySelector('.fn-galeriaMosaico'); 631 var boton = o.querySelector('.fn-galeriaAbrir'); 632 var cuantas = o.querySelector('.fn-galeriaCuantas'); 633 if (!mosaico || !boton) return; 634 635 // Coge el array de fotos de este Ãtem del CMS 636 var clave = Object.keys(datos || {})[0]; 637 var fotos = (datos && clave) ? datos[clave] : []; 638 fotos = (fotos || []).filter(function (url) { return typeof url === 'string' && url.trim(); }); 639 640 if (!fotos.length) { 641 o.hidden = true; 642 return; 643 } 644 645 var VISIBLES = 5; 646 var enMosaico = fotos.slice(0, VISIBLES); 647 var restantes = fotos.length - enMosaico.length; 648 649 enMosaico.forEach(function (url, i) { 650 var li = document.createElement('li'); 651 li.className = 'sta-galeria-fhe_celda' + (i === 0 ? ' sta-grande' : ''); 652 653 var ultima = (i === enMosaico.length - 1) && restantes > 0; 654 655 var img = document.createElement('img'); 656 img.src = url + '?width=' + (i === 0 ? 1200 : 600); 657 img.alt = ''; 658 img.setAttribute('role', 'presentation'); 659 img.loading = 'lazy'; 660 li.appendChild(img); 661 662 if (ultima) { 663 // La última celda aloja el botón de "ver todas" 664 li.appendChild(boton); 665 boton.hidden = false; 666 if (cuantas) cuantas.textContent = '+' + restantes; 667 } else { 668 var abrir = document.createElement('button'); 669 abrir.type = 'button'; 670 abrir.setAttribute('aria-label', boton.textContent.trim() || 'ver'); 671 abrir.addEventListener('click', function () { pedirModal(i); }); 672 li.appendChild(abrir); 673 } 674 675 mosaico.appendChild(li); 676 }); 677 678 function pedirModal(indice) { 679 document.dispatchEvent(new CustomEvent('galeriaAbrir', { 680 detail: { fotos: fotos, indice: indice } 681 })); 682 } 683 684 boton.addEventListener('click', function () { pedirModal(enMosaico.length - 1); }); 685}
685)(document.querySelector('.tpl-galeria-fhe'), window.json_galeria); 686/** tpl-galeria-fhe -- FIN */ 687 688 689/** tpl-modalGaleria-fhe -- INICIO */ 690(function (o) { 691 if (!o) return; 692 693 var img = o.querySelector('.fn-modalGaleriaImg'); 694 var actual = o.querySelector('.fn-modalGaleriaActual'); 695 var total = o.querySelector('.fn-modalGaleriaTotal'); 696 var prev = o.querySelector('.fn-modalGaleriaPrev'); 697 var next = o.querySelector('.fn-modalGaleriaNext'); 698 if (!img || !prev || !next) return; 699 700 var fotos = []; 701 var i = 0; 702 var previo = null; 703 var foco = 'button:not([disabled]), a[href], [tabindex]:not([tabindex="-1"])'; 704 705 var pintar = function () { 706 if (!fotos.length) return; 707 o.classList.add('sta-cargando'); 708 img.onload = function () { o.classList.remove('sta-cargando'); }; 709 img.src = fotos[i] + '?width=1600'; 710 img.alt = ''; 711 if (actual) actual.textContent = String(i + 1); 712 if (total) total.textContent = String(fotos.length); 713 var uno = fotos.length < 2; 714 prev.hidden = uno; 715 next.hidden = uno; 716 }; 717 718 var mover = function (paso) { 719 i = (i + paso + fotos.length) % fotos.length; 720 pintar(); 721 }; 722 723 var abrir = function (lista, indice) { 724 fotos = lista || []; 725 if (!fotos.length) return; 726 i = Math.min(Math.max(indice || 0, 0), fotos.length - 1); 727 previo = document.activeElement; 728 o.hidden = false; 729 o.classList.add('sta-abierto'); 730 document.documentElement.classList.add('sta-noScroll'); 731 pintar(); 732 var cerrarBtn = o.querySelector('.sta-modalGaleria-fhe_cerrar'); 733 if (cerrarBtn) cerrarBtn.focus(); 734 }; 735 736 var cerrar = function () { 737 o.hidden = true; 738 o.classList.remove('sta-abierto'); 739 document.documentElement.classList.remove('sta-noScroll'); 740 if (previo && previo.focus) previo.focus(); 741 }; 742 743 document.addEventListener('galeriaAbrir', function (e) { 744 abrir(e.detail && e.detail.fotos, e.detail && e.detail.indice); 745 }); 746 747 Array.prototype.forEach.call(o.querySelectorAll('.fn-modalGaleriaCerrar'), function (btn) { 748 btn.addEventListener('click', cerrar); 749 }); 750 751 prev.addEventListener('click', function () { mover(-1); }); 752 next.addEventListener('click', function () { mover(1); }); 753 754 o.addEventListener('keydown', function (e) { 755 if (e.key === 'Escape') { cerrar(); return; } 756 if (e.key === 'ArrowLeft') { mover(-1); return; } 757 if (e.key === 'ArrowRight') { mover(1); return; } 758 if (e.key !== 'Tab') return; 759 760 var lista = Array.prototype.filter.call(o.querySelectorAll(foco), function (el) { 761 return !el.hidden && el.offsetParent !== null; 762 }); 763 if (!lista.length) return; 764 765 var primero = lista[0]; 766 var ultimo = lista[lista.length - 1]; 767 768 if (e.shiftKey && document.activeElement === primero) { 769 e.preventDefault(); 770 ultimo.focus(); 771 } else if (!e.shiftKey && document.activeElement === ultimo) { 772 e.preventDefault(); 773 primero.focus(); 774 } 775 }); 776})(document.querySelector('.tpl-modalGaleria-fhe')); 777/** tpl-modalGaleria-fhe -- FIN */ 778 779 780/** tpl-formInscripcion-fhe -- INICIO */ 781(function (o, h) { 782 if (!o) return; 783 784 var form = o.querySelector('.fn-formInscripcion'); 785 if (!form) return; 786 787 var bloque = form.querySelector('.fn-formBloqueAcompanante'); 788 var asistentes = form.querySelector('.fn-formAsistentes'); 789 var aviso = form.querySelector('.fn-formAviso'); 790 var enviar = form.querySelector('[type="submit"]'); 791 var radios = form.querySelectorAll('.fn-formAcompanante'); 792 793 // Muestra u oculta el bloque del acompañante y sincroniza num_asistentes, 794 // que es lo que espera el backend antiguo. 795 var sincronizar = function () { 796 var acompanado = false; 797 Array.prototype.forEach.call(radios, function (r) { 798 if (r.checked && r.value === '1') acompanado = true; 799 }); 800 801 if (bloque) { 802 bloque.classList.toggle('sta-oculto', !acompanado); 803 Array.prototype.forEach.call(bloque.querySelectorAll('input'), function (input) { 804 input.required = acompanado; 805 if (!acompanado) { 806 input.value = ''; 807 input.removeAttribute('aria-invalid'); 808 limpiarError(input); 809 } 810 }); 811 } 812 813 if (asistentes) asistentes.value = acompanado ? '2' : '1'; 814 }; 815 816 // El checkbox de condiciones no vive en un _campo sino en el bloque _legal, 817 // asi que se contemplan los dos: si no, pintarError salia por el return y el 818 // envio se bloqueaba sin decirle nada al usuario. 819 var campoDe = function (input) { 820 return input.closest('.sta-formInscripcion-fhe_campo, .sta-formInscripcion-fhe_legal'); 821 }; 822 823 var limpiarError = function (input) { 824 var campo = campoDe(input); 825 if (!campo) return; 826 var hueco = campo.querySelector('.sta-formInscripcion-fhe_error');
827 if (hueco) hueco.textContent = ''; 828 }; 829 830 var pintarError = function (input, mensaje) { 831 input.setAttribute('aria-invalid', 'true'); 832 var campo = campoDe(input); 833 if (!campo) return; 834 var hueco = campo.querySelector('.sta-formInscripcion-fhe_error'); 835 if (hueco) hueco.textContent = mensaje; 836 }; 837 838 var validar = function () { 839 var primero = null; 840 841 Array.prototype.forEach.call(form.querySelectorAll('input, select'), function (input) { 842 if (input.type === 'hidden') return; 843 844 input.removeAttribute('aria-invalid'); 845 limpiarError(input); 846 847 if (!input.required) return; 848 849 var vacio = (input.type === 'checkbox' || input.type === 'radio') 850 ? !form.querySelector('[name="' + input.name + '"]:checked') 851 : !input.value.trim(); 852 853 var malMail = input.type === 'email' && input.value.trim() && 854 (h && h.mail ? !h.mail(input.value.trim()) : !/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(input.value.trim())); 855 856 if (vacio || malMail) { 857 pintarError(input, input.validationMessage || ''); 858 if (!primero) primero = input; 859 } 860 }); 861 862 if (primero) { 863 primero.focus(); 864 return false; 865 } 866 return true; 867 }; 868 869 Array.prototype.forEach.call(radios, function (r) { 870 r.addEventListener('change', sincronizar); 871 }); 872 873 Array.prototype.forEach.call(form.querySelectorAll('input, select'), function (input) { 874 input.addEventListener('input', function () { 875 input.removeAttribute('aria-invalid'); 876 limpiarError(input); 877 }); 878 }); 879 880 var dato = function (nombre) { 881 var el = form.querySelector('[name="' + nombre + '"]'); 882 return el && el.value ? el.value.trim() : ''; 883 }; 884 885 var acompanado = function () { 886 return !!form.querySelector('.fn-formAcompanante:checked[value="1"]'); 887 }; 888 889 // Arrays paralelos: cada tÃtulo se corresponde con el valor de su misma posición 890 var construirJson = function () { 891 var mail = dato('mail').toLowerCase(); 892 var con = acompanado(); 893 894 var title = [ 895 'Mail', 896 'Nombre', 897 'Apellidos', 898 'Telefono', 899 'Empresa', 900 'Cargo', 901 'Como nos ha conocido', 902 'Acompanante', 903 'Nombre acompanante', 904 'Apellidos acompanante', 905 'Empresa acompanante', 906 'Cargo acompanante', 907 'Num asistentes', 908 'Idioma', 909 'ID' 910 ]; 911 912 var values = [ 913 mail, 914 dato('name'), 915 dato('last'), 916 dato('tel'), 917 dato('comp'), 918 dato('cargo'), 919 dato('conocido') || '---', 920 con ? '1' : '0', 921 con ? (dato('name_2') || '---') : '---',
922 con ? (dato('last_2') || '---') : '---', 923 con ? (dato('comp_2') || '---') : '---', 924 con ? (dato('cargo_2') || '---') : '---', 925 asistentes ? asistentes.value : (con ? '2' : '1'), 926 dato('idioma') || document.documentElement.lang, 927 'foro-hotusa-explora-2026-000' 928 ]; 929 930 // El backend espera { id, title, new, nl }: title y new son dos arrays 931 // paralelos (cabecera y valor de cada columna) y nl, el mail de confirmación. 932 var json = { 933 id: 'foro-hotusa-explora-2026-000', 934 title: title, 935 new: values, 936 // Solo se manda la NL interna de aviso a secretarÃa (rutaH). Sin clave "ruta" 937 // el inscrito no recibe correo de confirmación. 938 nl: { 939 nlH: '[email protected]', 940 rutaH: '/images/comun/hotusaexplora/FHE2026/2026_ForoHotusaExplora_interna.html', 941 from: '[email protected]', 942 subject: 'Nueva inscripción a Foro Hotusa Explora', 943 // Cada clave alimenta un {tag} de la plantilla de la NL interna 944 tags: { 945 name: dato('name'), 946 surname: dato('last'), 947 mail: mail, 948 movil: dato('tel'), 949 business: dato('comp'), 950 cargo: dato('cargo'), 951 conocido: dato('conocido') || '---', 952 acompanante: con ? 'SI' : 'NO', 953 acom_name: con ? (dato('name_2') || '---') : '---',
954 acom_surname: con ? (dato('last_2') || '---') : '---', 955 acom_business: con ? (dato('comp_2') || '---') : '---', 956 acom_cargo: con ? (dato('cargo_2') || '---') : '---', 957 asistentes: asistentes ? asistentes.value : (con ? '2' : '1'), 958 idioma: dato('idioma') || document.documentElement.lang, 959 id: 'foro-hotusa-explora-2026-000', 960 campane: 'foro-hotusa-explora-2026-000', 961 url: location.origin + location.pathname, 962 modificado: 'NO' 963 } 964 } 965 }; 966 967 return json; 968 }; 969 970 var estado = function (ok) { 971 if (!aviso) return; 972 aviso.classList.toggle('sta-enviado', ok); 973 aviso.classList.toggle('sta-fallo', !ok); 974 }; 975 976 var terminar = function () { 977 if (enviar) enviar.disabled = false; 978 form.classList.remove('sta-enviando'); 979 }; 980 981 // El endpoint responde con un código numérico: 200 ok, 401 duplicado, 982 // 402 error de bbdd, 403 plantilla no encontrada, 410 inscripciones cerradas 983 var responder = function (codigo) { 984 if (codigo === 200) { 985 estado(true); 986 form.reset(); 987 sincronizar(); 988 return; 989 } 990 if (codigo === 403) console.warn('Inscripcion: ruta de plantilla no encontrada'); 991 estado(false); 992 }; 993 994 form.addEventListener('submit', function (e) { 995 if (!validar()) { 996 e.preventDefault(); 997 return; 998 } 999 1000 // Con action definido en el CMS se deja el envÃo nativo del navegador 1001 if (form.getAttribute('action')) return; 1002 1003 e.preventDefault(); 1004 1005 if (enviar) enviar.disabled = true; 1006 form.classList.add('sta-enviando'); 1007 if (aviso) aviso.classList.remove('sta-enviado', 'sta-fallo'); 1008 1009 var cuerpo = JSON.stringify(construirJson()); 1010 1011 fetch('https://nl.booking-channel.com/CLIENTES/nl/php/inscripcion2.0.php', { method: 'POST', body: cuerpo }) 1012 .then(function (r) { return r.json(); }) 1013 .then(responder) 1014 .catch(function () { estado(false); }) 1015 .then(terminar); 1016 }); 1017 1018 sincronizar(); 1019})(document.querySelector('.tpl-formInscripcion-fhe'), window.hotusa && hotusa()); 1020/** tpl-formInscripcion-fhe -- FIN */ 1021 1022 1023/** tpl-cabecera-fhe -- INICIO */ 1024(function (o) { 1025 if (!o) return; 1026 1027 // Scroll suave en el botón de ancla, respetando quien prefiere no animar 1028 var boton = o.querySelector('.sta-cabecera-fhe_boton.sta-primario'); 1029 if (!boton) return; 1030 1031 var href = boton.getAttribute('href') || ''; 1032 if (href.charAt(0) !== '#' || href.length < 2) return; 1033 1034 boton.addEventListener('click', function (e) { 1035 var destino = document.querySelector(href); 1036 if (!destino) return; 1037 1038 e.preventDefault(); 1039 var suave = !window.matchMedia('(prefers-reduced-motion: reduce)').matches; 1040 destino.scrollIntoView({ behavior: suave ? 'smooth' : 'auto', block: 'start' }); 1041 destino.setAttribute('tabindex', '-1'); 1042 destino.focus({ preventScroll: true }); 1043 }); 1044})(document.querySelector('.tpl-cabecera-fhe')); 1045/** tpl-cabecera-fhe -- FIN */ 1046 1047 1048 1049/** tpl-boxes-fhe -- INICIO */ 1050(function (o) { 1051 if (!o) return; 1052 1053 var capa = o.querySelector('.sta-boxes-fhe_fondo'); 1054 if (!capa) return; 1055 1056 // Sin canvas o si se prefiere no animar, el bloque se queda con su fondo plano 1057 var canvas = document.createElement('canvas'); 1058 if (!canvas.getContext) return; 1059 1060 var menos = window.matchMedia('(prefers-reduced-motion: reduce)'); 1061 if (menos.matches) return; 1062 1063 var ctx = canvas.getContext('2d'); 1064 capa.appendChild(canvas); 1065 1066 var CUANTOS = 3; // anillos, todos del mismo tamaño 1067 var RADIO = 190; // radio de referencia, se escala con el módulo 1068 var GROSOR = 0.22; // grosor del trazo respecto al radio (54/246 del SVG) 1069 var DESBORDE = 0.60; // cuánto puede salir del módulo: 60% de su radio 1070 var ALFA = 0.58; // igual que el stroke-opacity del SVG 1071 var VELOCIDAD = 0.35; // px por frame a 60fps 1072 var COLOR_ARRIBA = '#4C958E'; 1073 var COLOR_ABAJO = '#2F56A2'; 1074 1075 var dpr = 1; 1076 var ancho = 0; 1077 var alto = 0; 1078 var anillos = []; 1079 var ultimo = 0; 1080 var rafId = 0; 1081 var visible = false; 1082 1083 // Distancia mÃnima del centro al borde: con DESBORDE = 0.6 el anillo 1084 // asoma un 60% de su radio fuera del módulo, que lo recorta por overflow 1085 var margen = function (a) { 1086 return a.golpe * (1 - DESBORDE); 1087 }; 1088 1089 // El radio de colisión es el borde exterior del trazo, no la lÃnea media 1090 var crear = function (radio) { 1091 var grosor = radio * GROSOR; 1092 return { 1093 r: radio, 1094 grosor: grosor, 1095 golpe: radio + grosor * 0.5, 1096 x: 0, 1097 y: 0, 1098 vx: 0, 1099 vy: 0 1100 }; 1101 }; 1102 1103 // Reparte los anillos sin solaparse entre sÃ, con reintentos 1104 var colocar = function (a, puestos) { 1105 var m = margen(a); 1106 1107 for (var intento = 0; intento < 300; intento++) { 1108 a.x = m + Math.random() * Math.max(1, ancho - m * 2); 1109 a.y = m + Math.random() * Math.max(1, alto - m * 2); 1110 1111 var libre = true; 1112 for (var i = 0; i < puestos.length; i++) { 1113 var p = puestos[i]; 1114 var dx = p.x - a.x; 1115 var dy = p.y - a.y; 1116 var min = p.golpe + a.golpe; 1117 if (dx * dx + dy * dy < min * min) { libre = false; break; } 1118 } 1119 if (libre) return true; 1120 } 1121 return false;
1122 }; 1123 1124 var construir = function () { 1125 // Un único radio, escalado para que en móvil no tapen el contenido 1126 var ref = Math.min(ancho, alto); 1127 var escala = Math.max(0.35, Math.min(1, ref / 700)); 1128 var radio = RADIO * escala; 1129 1130 anillos = []; 1131 for (var i = 0; i < CUANTOS; i++) { 1132 var a = crear(radio); 1133 if (!colocar(a, anillos)) continue; 1134 1135 var angulo = Math.random() * 6.2832; 1136 var v = VELOCIDAD * (0.6 + Math.random() * 0.8); 1137 a.vx = Math.cos(angulo) * v; 1138 a.vy = Math.sin(angulo) * v; 1139 anillos.push(a); 1140 } 1141 }; 1142 1143 // Movimiento, rebote contra los bordes y choques elásticos entre anillos. 1144 // Al ser todos del mismo tamaño, el choque intercambia la componente normal 1145 var mover = function (dt) { 1146 var i, j; 1147 1148 for (i = 0; i < anillos.length; i++) { 1149 var a = anillos[i]; 1150 a.x += a.vx * dt; 1151 a.y += a.vy * dt; 1152 1153 var m = margen(a); 1154 if (a.x < m) { a.x = m; a.vx = Math.abs(a.vx); } 1155 if (a.x > ancho - m) { a.x = ancho - m; a.vx = -Math.abs(a.vx); } 1156 if (a.y < m) { a.y = m; a.vy = Math.abs(a.vy); } 1157 if (a.y > alto - m) { a.y = alto - m; a.vy = -Math.abs(a.vy); } 1158 } 1159 1160 for (i = 0; i < anillos.length; i++) { 1161 for (j = i + 1; j < anillos.length; j++) { 1162 var p = anillos[i]; 1163 var q = anillos[j]; 1164 1165 var dx = q.x - p.x; 1166 var dy = q.y - p.y; 1167 var min = p.golpe + q.golpe; 1168 var d2 = dx * dx + dy * dy; 1169 if (d2 >= min * min || d2 === 0) continue; 1170 1171 var d = Math.sqrt(d2); 1172 var nx = dx / d; 1173 var ny = dy / d; 1174 1175 // Se separan a partes iguales porque comparten radio 1176 var solape = (min - d) * 0.5; 1177 p.x -= nx * solape; 1178 p.y -= ny * solape; 1179 q.x += nx * solape; 1180 q.y += ny * solape; 1181 1182 var sep = (q.vx - p.vx) * nx + (q.vy - p.vy) * ny; 1183 if (sep > 0) continue; 1184 1185 p.vx += sep * nx; 1186 p.vy += sep * ny; 1187 q.vx -= sep * nx; 1188 q.vy -= sep * ny; 1189 } 1190 } 1191 }; 1192 1193 var medir = function () { 1194 var w = Math.max(1, Math.round(o.offsetWidth)); 1195 var h = Math.max(1, Math.round(o.offsetHeight)); 1196 if (w === ancho && h === alto) return false; 1197 1198 ancho = w; 1199 alto = h; 1200 dpr = Math.min(window.devicePixelRatio || 1, 2); 1201 1202 canvas.width = Math.round(ancho * dpr); 1203 canvas.height = Math.round(alto * dpr); 1204 ctx.setTransform(dpr, 0, 0, dpr, 0, 0); 1205 1206 construir(); 1207 return true; 1208 }; 1209 1210 var pintar = function () { 1211 ctx.clearRect(0, 0, ancho, alto); 1212 ctx.globalAlpha = ALFA; 1213 1214 for (var i = 0; i < anillos.length; i++) { 1215 var a = anillos[i]; 1216 1217 // Degradado vertical de borde a borde del anillo, como en el SVG 1218 var grad = ctx.createLinearGradient(a.x, a.y - a.golpe, a.x, a.y + a.golpe); 1219 grad.addColorStop(0, COLOR_ARRIBA); 1220 grad.addColorStop(1, COLOR_ABAJO); 1221 1222 ctx.beginPath(); 1223 ctx.arc(a.x, a.y, a.r, 0, 6.2832); 1224 ctx.lineWidth = a.grosor; 1225 ctx.strokeStyle = grad; 1226 ctx.stroke(); 1227 } 1228 1229 ctx.globalAlpha = 1; 1230 }; 1231 1232 var animar = function (ahora) { 1233 rafId = window.requestAnimationFrame(animar); 1234 1235 // dt en frames de 60fps, acotado para no saltar al volver de otra pestaña 1236 var delta = ultimo ? (ahora - ultimo) : 16.7; 1237 ultimo = ahora; 1238 mover(Math.min(delta / 16.7, 3)); 1239 pintar(); 1240 }; 1241 1242 var arrancar = function () { 1243 if (rafId) return; 1244 ultimo = 0; 1245 rafId = window.requestAnimationFrame(animar); 1246 }; 1247 1248 var parar = function () { 1249 if (!rafId) return; 1250 window.cancelAnimationFrame(rafId); 1251 rafId = 0; 1252 }; 1253 1254 medir(); 1255 pintar(); 1256 1257 var alRedimensionar = function () { 1258 if (medir() && !rafId) pintar(); 1259 }; 1260 1261 if (window.ResizeObserver) { 1262 new ResizeObserver(alRedimensionar).observe(o); 1263 } else { 1264 window.addEventListener('resize', alRedimensionar); 1265 } 1266 1267 // Sólo se anima mientras el módulo está en pantalla 1268 if (window.IntersectionObserver) { 1269 new IntersectionObserver(function (entradas) { 1270 visible = entradas[0].isIntersecting; 1271 if (visible && !document.hidden) arrancar(); 1272 else parar(); 1273 }, { threshold: 0 }).observe(o); 1274 } else { 1275 visible = true; 1276 arrancar(); 1277 } 1278 1279 document.addEventListener('visibilitychange', function () { 1280 if (document.hidden) parar(); 1281 else if (visible) arrancar(); 1282 }); 1283})(document.querySelector('.tpl-boxes-fhe')); 1284/** tpl-boxes-fhe -- FIN */ 1285 1286
1287/** tpl-cookies-fhe -- INICIO */ 1288/* Puente entre el botón "Configurar mis cookies" de la landing de cookies y el 1289 panel del script global (/ja/globalCookies/), que es quien manda aquÃ. 1290 1291 head-fhe sólo carga ese script cuando NO hay cookie `controlCookies`: si el 1292 usuario ya eligió, en la página no hay banner ninguno y hay que inyectarlo 1293 antes de abrirlo. El panel se controla con dos checkboxes que pinta el propio 1294 script: #sta-cookies (banner visible) y #sta-cookies_config (pestaña de 1295 configuración desplegada). */ 1296(function (boton) { 1297 if (!boton) return; 1298 1299 var RUTA_CSS = '/ja/globalCookies/style.css'; 1300 var RUTA_JS = '/ja/globalCookies/controlCookiesVanilla.js'; 1301 1302 var abrirPanel = function () { 1303 var banner = document.getElementById('sta-cookies'); 1304 var config = document.getElementById('sta-cookies_config'); 1305 if (!banner) return false; 1306 1307 banner.checked = true; 1308 if (config) config.checked = true; 1309 1310 var dialogo = document.querySelector('.tpl-cookie'); 1311 if (dialogo) dialogo.focus(); 1312 return true; 1313 }; 1314 1315 // El script ya inyectó el banner: basta con abrirlo 1316 var yaCargado = function () { 1317 return !!document.querySelector('.tpl-cookie'); 1318 }; 1319 1320 var cargar = function () { 1321 if (!document.querySelector('link[href^="' + RUTA_CSS + '"]')) { 1322 var css = document.createElement('link'); 1323 css.rel = 'stylesheet'; 1324 css.href = RUTA_CSS; 1325 document.body.appendChild(css); 1326 } 1327 1328 var js = document.createElement('script'); 1329 js.src = RUTA_JS; 1330 // El script se autoejecuta al cargar; el panel se abre cuando ya está el DOM 1331 js.onload = abrirPanel; 1332 document.body.appendChild(js); 1333 }; 1334 1335 boton.addEventListener('click', function () { 1336 if (yaCargado()) { 1337 abrirPanel(); 1338 return; 1339 } 1340 // Sin window.g_cookies_content no hay textos y el script rompe: lo pinta head-fhe 1341 if (!window.g_cookies_content) return; 1342 cargar(); 1343 }); 1344})(document.querySelector('.fn-abrirCookies')); 1345/** tpl-cookies-fhe -- FIN */
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.