/* ---- raiz del diseno (ver preludio en subir_js.py) ---- */ ( function () { var raiz = document.getElementById( 'ah-refocus-root' ); if ( raiz ) { raiz.classList.add( 'ah-page-nosotros', 'ah-nosotros' ); } } )(); ; /* ---- assets/js/reviews.js ---- */ /** * Carrusel de reseñas: conecta las flechas al scroll horizontal de las tarjetas. * El markup vive en el contenido de la página, así que este script no asume * cuántas reseñas hay: mide el ancho real de una tarjeta en cada movimiento. */ ( function () { 'use strict'; function initCarousel( carousel ) { var track = carousel.querySelector( '.refocus-reviews-cards' ); var arrows = carousel.querySelectorAll( '.refocus-reviews-arrow' ); if ( ! track || arrows.length < 2 ) { return; } var prev = arrows[ 0 ]; var next = arrows[ 1 ]; function step() { var card = track.querySelector( '.refocus-review-card' ); if ( ! card ) { return track.clientWidth; } var styles = window.getComputedStyle( track ); var gap = parseFloat( styles.columnGap || styles.gap ) || 0; return card.getBoundingClientRect().width + gap; } function syncArrows() { var maxScroll = track.scrollWidth - track.clientWidth; prev.disabled = track.scrollLeft <= 1; next.disabled = track.scrollLeft >= maxScroll - 1; } prev.addEventListener( 'click', function () { track.scrollBy( { left: -step(), behavior: 'smooth' } ); } ); next.addEventListener( 'click', function () { track.scrollBy( { left: step(), behavior: 'smooth' } ); } ); track.addEventListener( 'scroll', syncArrows, { passive: true } ); window.addEventListener( 'resize', syncArrows ); syncArrows(); } function init() { var carousels = document.querySelectorAll( '.refocus-reviews-carousel' ); Array.prototype.forEach.call( carousels, initCarousel ); } if ( 'loading' === document.readyState ) { document.addEventListener( 'DOMContentLoaded', init ); } else { init(); } }() ); ; /* ---- assets/js/galerias.js ---- */ /** * Galerías de la página Nosotros. * * Ganchos (clases derivadas de los nombres de capa del diseño): * .ah-galeria-section -> galería del local * .ah-equipo-section -> carrusel de equipo * En ambos: .ah-slide-active (foto en foco), laterales, .ah-prev/.ah-next, * .ah-counter y, si existen, .ah-thumb-strip y .ah-progress. * * Tres cosas del markup exportado condicionan el código: * 1. La foto de cada miniatura NO está en la miniatura sino en su hijo * .ah-image; leerla del contenedor devuelve vacío. * 2. Los slides usan background-image de DOS capas (degradado + foto): al * cambiar hay que reemplazar solo la parte url() y conservar el * degradado, o el texto encima queda ilegible. * 3. Los laterales tienen nombres distintos en cada bloque. */ ( function () { 'use strict'; var RE_URL = /url\((["']?)(.*?)\1\)/; // Los dos valores del mockup para la miniatura resaltada. var ORO_BORDE = '2px solid #E8C593'; var ORO_TEXTO = '#F0D7AC'; function elImagen( el ) { return el ? ( el.querySelector( '.ah-image' ) || el ) : null; } function urlDe( el ) { var origen = elImagen( el ); if ( ! origen ) { return ''; } var bg = origen.style.backgroundImage || window.getComputedStyle( origen ).backgroundImage || ''; var m = RE_URL.exec( bg ); return m ? m[ 2 ] : ''; } function setUrl( el, url ) { if ( ! el || ! url ) { return; } var bg = el.style.backgroundImage || window.getComputedStyle( el ).backgroundImage || ''; if ( RE_URL.test( bg ) ) { el.style.backgroundImage = bg.replace( RE_URL, 'url("' + url + '")' ); } else { el.style.backgroundImage = ( bg && 'none' !== bg ? bg + ', ' : '' ) + 'url("' + url + '")'; } } function dosDigitos( n ) { return ( n < 10 ? '0' : '' ) + n; } function activarBoton( boton, etiqueta, alClic ) { if ( ! boton ) { return; } boton.style.cursor = 'pointer'; boton.setAttribute( 'role', 'button' ); boton.setAttribute( 'tabindex', '0' ); boton.setAttribute( 'aria-label', etiqueta ); boton.addEventListener( 'click', alClic ); boton.addEventListener( 'keydown', function ( e ) { if ( 'Enter' === e.key || ' ' === e.key ) { e.preventDefault(); alClic(); } } ); } /** * Arrastre con mouse/trackpad y deslizamiento horizontal de rueda. * Se usa Pointer Events para cubrir mouse y touch con un solo camino. */ function activarArrastre( zona, alAvanzar, alRetroceder ) { if ( ! zona ) { return; } var UMBRAL = 45; var inicioX = null; var arrastrando = false; zona.style.cursor = 'grab'; zona.style.touchAction = 'pan-y'; zona.addEventListener( 'pointerdown', function ( e ) { if ( e.button && 0 !== e.button ) { return; } inicioX = e.clientX; arrastrando = true; zona.style.cursor = 'grabbing'; } ); function terminar( e ) { if ( ! arrastrando || null === inicioX ) { return; } var delta = e.clientX - inicioX; arrastrando = false; inicioX = null; zona.style.cursor = 'grab'; if ( Math.abs( delta ) < UMBRAL ) { return; } // Arrastrar hacia la izquierda avanza, como en cualquier carrusel. if ( delta < 0 ) { alAvanzar(); } else { alRetroceder(); } } zona.addEventListener( 'pointerup', terminar ); zona.addEventListener( 'pointercancel', function () { arrastrando = false; inicioX = null; zona.style.cursor = 'grab'; } ); zona.addEventListener( 'pointerleave', function ( e ) { if ( arrastrando ) { terminar( e ); } } ); // Un clic no debe arrastrar la imagen de fondo del navegador. zona.addEventListener( 'dragstart', function ( e ) { e.preventDefault(); } ); // Deslizamiento horizontal de trackpad. Se acumula para no saltar // varias fotos con un solo gesto. var acumulado = 0; var ultimo = 0; zona.addEventListener( 'wheel', function ( e ) { if ( Math.abs( e.deltaX ) <= Math.abs( e.deltaY ) ) { return; // gesto vertical: es scroll de la página } e.preventDefault(); var ahora = Date.now(); if ( ahora - ultimo > 400 ) { acumulado = 0; } ultimo = ahora; acumulado += e.deltaX; if ( acumulado > 60 ) { acumulado = 0; alAvanzar(); } else if ( acumulado < -60 ) { acumulado = 0; alRetroceder(); } }, { passive: false } ); } function initCarrusel( seccionSel ) { var seccion = document.querySelector( seccionSel ); if ( ! seccion ) { return; } var stage = seccion.querySelector( '.ah-slide-active' ); if ( ! stage ) { return; } var peekPrev = seccion.querySelector( '.ah-peek-prev, .ah-slide-peek-03' ); var peekNext = seccion.querySelector( '.ah-peek-next, .ah-slide-peek-02' ); var thumbs = Array.prototype.slice.call( seccion.querySelectorAll( '.ah-thumb-strip > [class*="ah-thumb-"]' ) ); var fotos = thumbs.length ? thumbs.map( urlDe ).filter( Boolean ) : [ stage, peekPrev, peekNext ].map( urlDe ).filter( Boolean ); if ( fotos.length < 2 ) { return; } // Texto por foto: viene de los data-* que pone la plantilla. var textos = thumbs.map( function ( t ) { return { titulo: t.getAttribute( 'data-ah-title' ) || '', texto: t.getAttribute( 'data-ah-text' ) || '', }; } ); var elNum = seccion.querySelector( '.ah-num' ); var elTitulo = seccion.querySelector( '.ah-caption-text .ah-title' ); var elTexto = seccion.querySelector( '.ah-caption-text .ah-text' ); var elContador = seccion.querySelector( '.ah-counter' ); var segmentos = Array.prototype.slice.call( seccion.querySelectorAll( '.ah-progress > [class*="ah-seg-"]' ) ); var actual = 0; function pintarProgreso() { if ( ! segmentos.length ) { return; } // Un segmento por foto: los que sobran se ocultan. Antes se // repartían proporcionalmente (4 barras para 3 fotos) y el // resultado no significaba nada: pasaba de 1 a 3 barras marcadas. segmentos.forEach( function ( s, i ) { if ( i >= fotos.length ) { s.style.display = 'none'; return; } s.style.display = ''; s.style.opacity = i === actual ? '1' : '0.25'; } ); } function mostrar( i ) { actual = ( ( i % fotos.length ) + fotos.length ) % fotos.length; setUrl( stage, fotos[ actual ] ); setUrl( peekNext, fotos[ ( actual + 1 ) % fotos.length ] ); setUrl( peekPrev, fotos[ ( actual - 1 + fotos.length ) % fotos.length ] ); if ( elNum ) { elNum.textContent = dosDigitos( actual + 1 ); } if ( textos[ actual ] ) { if ( elTitulo && textos[ actual ].titulo ) { elTitulo.textContent = textos[ actual ].titulo; } if ( elTexto && textos[ actual ].texto ) { elTexto.textContent = textos[ actual ].texto; } } if ( elContador ) { var hoja = elContador.querySelector( '.ah-value' ) || elContador; hoja.textContent = dosDigitos( actual + 1 ) + ' / ' + dosDigitos( fotos.length ); } // Se fija el estado de TODAS, activa y no activas, con valores // explícitos. Poner '' en las no activas devolvía el estilo inline // del mockup, y la miniatura 2 —que el diseño trae resaltada— // quedaba marcada para siempre además de la real. // // El resaltado va en la FOTO y en el rótulo, no en el contenedor: // así lo hace el diseño (la miniatura 2 del mockup trae el borde // dorado sobre .ah-image). Poniéndolo en el contenedor salía un // rectángulo que envolvía foto + texto, y encima en azul. thumbs.forEach( function ( t, k ) { var activo = k === actual; var foto = t.querySelector( '.ah-image' ); var rotulo = t.querySelector( '.ah-label' ); if ( foto ) { foto.style.outline = activo ? ORO_BORDE : '1px solid #FFFFFF1F'; foto.style.outlineOffset = activo ? '-1px' : '-0.5px'; foto.style.opacity = activo ? '1' : '0.42'; } if ( rotulo ) { rotulo.style.color = activo ? ORO_TEXTO : '#FFFFFF6B'; } t.style.outline = ''; t.style.outlineOffset = ''; t.style.opacity = ''; t.setAttribute( 'aria-current', activo ? 'true' : 'false' ); } ); pintarProgreso(); } function avanzar() { mostrar( actual + 1 ); } function retroceder() { mostrar( actual - 1 ); } thumbs.forEach( function ( t, i ) { activarBoton( t, ( textos[ i ] && textos[ i ].titulo ) || 'Ver foto ' + ( i + 1 ), function () { mostrar( i ); } ); } ); activarBoton( seccion.querySelector( '.ah-prev' ), 'Foto anterior', retroceder ); activarBoton( seccion.querySelector( '.ah-next' ), 'Foto siguiente', avanzar ); activarArrastre( seccion.querySelector( '.ah-carousel-stage, .ah-equipo-carousel, .ah-track' ) || stage, avanzar, retroceder ); // Arranca siempre en la primera foto. mostrar( 0 ); } function init() { initCarrusel( '.ah-galeria-section' ); initCarrusel( '.ah-equipo-section' ); } if ( 'loading' === document.readyState ) { document.addEventListener( 'DOMContentLoaded', init ); } else { init(); } }() );