(function() {
const sliderConfig = {
dotColor: "#1E40AF",
inactiveDotColor: "rgba(0, 0, 0, 0.3)",
arrowColor: "#1E40AF",
arrowBgColor: "#ffffff",
arrowBgOpacity: 0.2,
arrowVertical: "center",
arrowDistance: 0.5,
arrowSize: 48,
showArrowBg: true,
transitionSpeed: 0.6,
autoplayInterval: 4,
slideGap: 10,
activeScale: 0.95,
inactiveOpacity: 0.6,
enableAutoplay: true,
enableDrag: true,
dragSensitivity: 80,
showArrows: true,
slidesPerView: 3,
advanceStep: "1"
};
function initSlider() {
const sliderContainers = document.querySelectorAll('[data-slider]:not([data-slider-initialized="true"])');
sliderContainers.forEach(sliderContainer => {
const originalSlides = Array.from(sliderContainer.querySelectorAll('[data-slide]'));
const slidesCount = originalSlides.length;
if (slidesCount === 0) return;
let currentSlide = 0;
let autoplayInterval;
let isDragging = false;
let startX = 0;
let startY = 0;
let currentX = 0;
let currentY = 0;
let initialTransform = 0;
let hasDetectedDirection = false;
let allowHorizontalDrag = false;
// Detectar si es móvil
const isMobile = window.innerWidth <= 768;
const effectiveSlidesPerView = isMobile ? 1 : sliderConfig.slidesPerView;
const effectiveAdvanceStep = sliderConfig.advanceStep === 'group' ? effectiveSlidesPerView : 1;
sliderContainer.style.position = 'relative';
sliderContainer.style.overflow = 'hidden';
sliderContainer.style.paddingBottom = '4rem';
sliderContainer.style.cursor = sliderConfig.enableDrag ? 'grab' : 'default';
sliderContainer.style.userSelect = 'none';
sliderContainer.style.touchAction = 'pan-y pinch-zoom';
const slidesWrapper = document.createElement('div');
slidesWrapper.style.cssText = `
display: flex;
transition: transform ${sliderConfig.transitionSpeed}s cubic-bezier(0.25, 0.8, 0.25, 1);
width: 100%;
height: 100%;
will-change: transform;
gap: ${sliderConfig.slideGap}px;
`;
// BUCLE INFINITO: Clonación inteligente para evitar espacios vacíos
let allSlides = [...originalSlides];
if (effectiveSlidesPerView > 1 && slidesCount >= effectiveSlidesPerView) {
// Clonar slides al final para el bucle infinito
const clonesNeeded = effectiveSlidesPerView - 1;
for (let i = 0; i < clonesNeeded; i++) {
const clone = originalSlides[i].cloneNode(true);
clone.setAttribute('data-slide-clone', 'end');
allSlides.push(clone);
}
// Clonar slides al inicio para navegación hacia atrás
const startClones = [];
for (let i = slidesCount - clonesNeeded; i < slidesCount; i++) {
const clone = originalSlides[i].cloneNode(true);
clone.setAttribute('data-slide-clone', 'start');
startClones.push(clone);
}
allSlides = [...startClones, ...allSlides];
// Ajustar índice inicial para compensar los clones del inicio
currentSlide = clonesNeeded;
}
// Configurar ancho de slides
const totalSlidesInView = allSlides.length;
const slideWidthPercentage = effectiveSlidesPerView > 1 ?
(100 / effectiveSlidesPerView) : 100;
allSlides.forEach((slide, index) => {
slide.style.cssText = `
width: calc(${slideWidthPercentage}% - ${sliderConfig.slideGap * (effectiveSlidesPerView - 1) / effectiveSlidesPerView}px);
flex-shrink: 0;
opacity: 1;
transform: scale(1);
transition: all ${sliderConfig.transitionSpeed}s cubic-bezier(0.25, 0.8, 0.25, 1);
pointer-events: none;
`;
slidesWrapper.appendChild(slide);
});
sliderContainer.innerHTML = '';
sliderContainer.appendChild(slidesWrapper);
// Crear dots basados en slides originales únicamente
const dotsContainer = document.createElement('div');
dotsContainer.style.cssText = `
position: absolute;
bottom: 1.5rem;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 0.75rem;
z-index: 10;
`;
for (let i = 0; i < slidesCount; i++) {
const dot = document.createElement('button');
dot.style.cssText = `
width: ${i === 0 ? '1.5rem' : '0.375rem'};
height: 0.375rem;
border-radius: 9999px;
background-color: ${i === 0 ? sliderConfig.dotColor : sliderConfig.inactiveDotColor};
border: none;
padding: 0;
cursor: pointer;
transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
`;
dot.addEventListener('click', () => {
const targetIndex = effectiveSlidesPerView > 1 && slidesCount >= effectiveSlidesPerView ?
i + (effectiveSlidesPerView - 1) : i;
goToSlide(targetIndex);
});
dot.addEventListener('touchend', (e) => {
e.preventDefault();
const targetIndex = effectiveSlidesPerView > 1 && slidesCount >= effectiveSlidesPerView ?
i + (effectiveSlidesPerView - 1) : i;
goToSlide(targetIndex);
}, { passive: false });
dotsContainer.appendChild(dot);
}
sliderContainer.appendChild(dotsContainer);
if (sliderConfig.showArrows) {
// Calcular posición vertical
let verticalPosition = '50%';
let verticalTransform = 'translateY(-50%)';
if (sliderConfig.arrowVertical === 'top') {
verticalPosition = `${sliderConfig.arrowDistance}rem`;
verticalTransform = 'translateY(0)';
} else if (sliderConfig.arrowVertical === 'bottom') {
verticalPosition = 'auto';
verticalTransform = 'translateY(0)';
}
const prevArrow = document.createElement('button');
prevArrow.className = 'nav-arrows prev';
prevArrow.setAttribute('aria-label', 'Previous slide');
let arrowStyles = `
position: absolute;
${sliderConfig.arrowVertical === 'top' ? 'top' : (sliderConfig.arrowVertical === 'bottom' ? 'bottom' : 'top')}: ${verticalPosition};
${sliderConfig.arrowVertical === 'bottom' ? 'bottom: ' + sliderConfig.arrowDistance + 'rem;' : ''}
left: ${sliderConfig.arrowDistance}rem;
transform: ${verticalTransform};
width: ${sliderConfig.arrowSize}px;
height: ${sliderConfig.arrowSize}px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
user-select: none;
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
border: none;
`;
if (sliderConfig.showArrowBg) {
const r = parseInt(sliderConfig.arrowBgColor.slice(1, 3), 16);
const g = parseInt(sliderConfig.arrowBgColor.slice(3, 5), 16);
const b = parseInt(sliderConfig.arrowBgColor.slice(5, 7), 16);
arrowStyles += `
background: rgba(${r}, ${g}, ${b}, ${sliderConfig.arrowBgOpacity});
border-radius: 50%;
backdrop-filter: blur(10px);
`;
} else {
arrowStyles += `
background: transparent;
`;
}
prevArrow.style.cssText = arrowStyles;
prevArrow.innerHTML = `<span style="
width: ${Math.max(8, sliderConfig.arrowSize * 0.25)}px;
height: ${Math.max(8, sliderConfig.arrowSize * 0.25)}px;
border-top: 2px solid ${sliderConfig.arrowColor};
border-right: 2px solid ${sliderConfig.arrowColor};
transform: rotate(-135deg);
margin-right: -2px;
transition: all 0.3s ease;
"></span>`;
const nextArrow = document.createElement('button');
nextArrow.className = 'nav-arrows next';
nextArrow.setAttribute('aria-label', 'Next slide');
nextArrow.style.cssText = arrowStyles.replace('left:', 'right:');
nextArrow.innerHTML = `<span style="
width: ${Math.max(8, sliderConfig.arrowSize * 0.25)}px;
height: ${Math.max(8, sliderConfig.arrowSize * 0.25)}px;
border-top: 2px solid ${sliderConfig.arrowColor};
border-right: 2px solid ${sliderConfig.arrowColor};
transform: rotate(45deg);
margin-left: -2px;
transition: all 0.3s ease;
"></span>`;
function addArrowEvents(arrow, callback) {
arrow.addEventListener('click', callback);
arrow.addEventListener('touchend', (e) => {
e.preventDefault();
callback();
}, { passive: false });
if (sliderConfig.showArrowBg) {
arrow.addEventListener('mouseenter', function() {
const r = parseInt(sliderConfig.arrowBgColor.slice(1, 3), 16);
const g = parseInt(sliderConfig.arrowBgColor.slice(3, 5), 16);
const b = parseInt(sliderConfig.arrowBgColor.slice(5, 7), 16);
this.style.background = `rgba(${r}, ${g}, ${b}, ${Math.min(1, sliderConfig.arrowBgOpacity + 0.3)})`;
this.style.transform = `${verticalTransform} scale(1.1)`;
});
arrow.addEventListener('mouseleave', function() {
const r = parseInt(sliderConfig.arrowBgColor.slice(1, 3), 16);
const g = parseInt(sliderConfig.arrowBgColor.slice(3, 5), 16);
const b = parseInt(sliderConfig.arrowBgColor.slice(5, 7), 16);
this.style.background = `rgba(${r}, ${g}, ${b}, ${sliderConfig.arrowBgOpacity})`;
this.style.transform = verticalTransform;
});
}
}
addArrowEvents(prevArrow, () => {
let newIndex = currentSlide - effectiveAdvanceStep;
// Control del bucle infinito hacia atrás
if (effectiveSlidesPerView > 1 && slidesCount >= effectiveSlidesPerView) {
if (newIndex < (effectiveSlidesPerView - 1)) {
// Saltar al final (antes de los clones finales)
newIndex = slidesCount + (effectiveSlidesPerView - 1) - effectiveAdvanceStep;
slidesWrapper.style.transition = 'none';
goToSlideInstant(newIndex);
setTimeout(() => {
slidesWrapper.style.transition = `transform ${sliderConfig.transitionSpeed}s cubic-bezier(0.25, 0.8, 0.25, 1)`;
goToSlide(newIndex);
}, 50);
return;
}
} else {
if (newIndex < 0) {
newIndex = slidesCount - 1;
}
}
goToSlide(newIndex);
});
addArrowEvents(nextArrow, () => {
let newIndex = currentSlide + effectiveAdvanceStep;
// Control del bucle infinito hacia adelante
if (effectiveSlidesPerView > 1 && slidesCount >= effectiveSlidesPerView) {
if (newIndex >= slidesCount + (effectiveSlidesPerView - 1)) {
// Saltar al inicio (después de los clones iniciales)
newIndex = (effectiveSlidesPerView - 1) + effectiveAdvanceStep;
slidesWrapper.style.transition = 'none';
goToSlideInstant(newIndex);
setTimeout(() => {
slidesWrapper.style.transition = `transform ${sliderConfig.transitionSpeed}s cubic-bezier(0.25, 0.8, 0.25, 1)`;
goToSlide(newIndex);
}, 50);
return;
}
} else {
if (newIndex >= slidesCount) {
newIndex = 0;
}
}
goToSlide(newIndex);
});
sliderContainer.appendChild(prevArrow);
sliderContainer.appendChild(nextArrow);
}
if (sliderConfig.enableDrag) {
setupDragging();
}
sliderContainer.setAttribute('data-slider-initialized', 'true');
goToSlide(currentSlide);
function goToSlideInstant(index) {
currentSlide = index;
updateSlidePosition();
updateVisualStates();
}
function goToSlide(index) {
currentSlide = index;
updateSlidePosition();
updateVisualStates();
}
function updateSlidePosition() {
const containerWidth = sliderContainer.offsetWidth;
const slideWidthPx = (containerWidth - (sliderConfig.slideGap * (effectiveSlidesPerView - 1))) / effectiveSlidesPerView;
const totalSlideWidth = slideWidthPx + sliderConfig.slideGap;
const translateValue = -(currentSlide * totalSlideWidth);
slidesWrapper.style.transform = `translateX(${translateValue}px)`;
}
function updateVisualStates() {
const allSlides = slidesWrapper.children;
// Actualizar estilos de slides
for (let i = 0; i < allSlides.length; i++) {
const isVisible = effectiveSlidesPerView === 1 ?
i === currentSlide :
i >= currentSlide && i < (currentSlide + effectiveSlidesPerView);
if (isVisible) {
allSlides[i].style.opacity = '1';
allSlides[i].style.transform = `scale(${sliderConfig.activeScale})`;
} else {
allSlides[i].style.opacity = sliderConfig.inactiveOpacity.toString();
allSlides[i].style.transform = 'scale(1)';
}
}
// Actualizar dots (basado en slides originales)
const dots = dotsContainer.children;
let activeDotIndex = currentSlide;
if (effectiveSlidesPerView > 1 && slidesCount >= effectiveSlidesPerView) {
// Compensar por los clones del inicio
activeDotIndex = (currentSlide - (effectiveSlidesPerView - 1) + slidesCount) % slidesCount;
}
for (let i = 0; i < dots.length; i++) {
if (i === activeDotIndex) {
dots[i].style.width = '1.5rem';
dots[i].style.backgroundColor = sliderConfig.dotColor;
} else {
dots[i].style.width = '0.375rem';
dots[i].style.backgroundColor = sliderConfig.inactiveDotColor;
}
}
}
function setupDragging() {
function getX(event) {
return event.type.includes('mouse') ? event.clientX : event.touches[0].clientX;
}
function getY(event) {
return event.type.includes('mouse') ? event.clientY : event.touches[0].clientY;
}
function handleStart(event) {
if (!sliderConfig.enableDrag) return;
isDragging = true;
hasDetectedDirection = false;
allowHorizontalDrag = false;
startX = getX(event);
startY = getY(event);
currentX = startX;
currentY = startY;
const transform = getComputedStyle(slidesWrapper).transform;
if (transform !== 'none') {
const matrix = new DOMMatrixReadOnly(transform);
initialTransform = matrix.m41;
} else {
initialTransform = 0;
}
slidesWrapper.style.transition = 'none';
if (autoplayInterval) {
clearInterval(autoplayInterval);
}
}
function handleMove(event) {
if (!isDragging) return;
currentX = getX(event);
currentY = getY(event);
const deltaX = Math.abs(currentX - startX);
const deltaY = Math.abs(currentY - startY);
if (!hasDetectedDirection && (deltaX > 8 || deltaY > 8)) {
hasDetectedDirection = true;
if (deltaX > deltaY * 1.5) {
allowHorizontalDrag = true;
sliderContainer.style.cursor = 'grabbing';
event.preventDefault();
} else {
isDragging = false;
slidesWrapper.style.transition = `transform ${sliderConfig.transitionSpeed}s cubic-bezier(0.25, 0.8, 0.25, 1)`;
sliderContainer.style.cursor = sliderConfig.enableDrag ? 'grab' : 'default';
return;
}
}
if (allowHorizontalDrag) {
const deltaXMove = currentX - startX;
const newTransform = initialTransform + deltaXMove;
slidesWrapper.style.transform = `translateX(${newTransform}px)`;
event.preventDefault();
}
}
function handleEnd(event) {
if (!isDragging || !allowHorizontalDrag) {
isDragging = false;
return;
}
isDragging = false;
sliderContainer.style.cursor = sliderConfig.enableDrag ? 'grab' : 'default';
slidesWrapper.style.transition = `transform ${sliderConfig.transitionSpeed}s cubic-bezier(0.25, 0.8, 0.25, 1)`;
const deltaX = currentX - startX;
const sensitivity = Math.max(20, 100 - sliderConfig.dragSensitivity);
const threshold = sliderContainer.offsetWidth / (100 / sensitivity);
let newIndex = currentSlide;
if (Math.abs(deltaX) > threshold) {
if (deltaX < 0) {
// Avanzar
newIndex = currentSlide + effectiveAdvanceStep;
if (effectiveSlidesPerView > 1 && slidesCount >= effectiveSlidesPerView) {
if (newIndex >= slidesCount + (effectiveSlidesPerView - 1)) {
newIndex = (effectiveSlidesPerView - 1) + effectiveAdvanceStep;
slidesWrapper.style.transition = 'none';
goToSlideInstant(newIndex);
setTimeout(() => {
slidesWrapper.style.transition = `transform ${sliderConfig.transitionSpeed}s cubic-bezier(0.25, 0.8, 0.25, 1)`;
goToSlide(newIndex);
}, 50);
if (sliderConfig.enableAutoplay) startAutoplay();
return;
}
} else {
if (newIndex >= slidesCount) newIndex = 0;
}
} else {
// Retroceder
newIndex = currentSlide - effectiveAdvanceStep;
if (effectiveSlidesPerView > 1 && slidesCount >= effectiveSlidesPerView) {
if (newIndex < (effectiveSlidesPerView - 1)) {
newIndex = slidesCount + (effectiveSlidesPerView - 1) - effectiveAdvanceStep;
slidesWrapper.style.transition = 'none';
goToSlideInstant(newIndex);
setTimeout(() => {
slidesWrapper.style.transition = `transform ${sliderConfig.transitionSpeed}s cubic-bezier(0.25, 0.8, 0.25, 1)`;
goToSlide(newIndex);
}, 50);
if (sliderConfig.enableAutoplay) startAutoplay();
return;
}
} else {
if (newIndex < 0) newIndex = slidesCount - 1;
}
}
}
goToSlide(newIndex);
if (sliderConfig.enableAutoplay) {
startAutoplay();
}
}
sliderContainer.addEventListener('mousedown', handleStart);
sliderContainer.addEventListener('touchstart', handleStart, { passive: true });
document.addEventListener('mousemove', handleMove);
document.addEventListener('touchmove', handleMove, { passive: false });
document.addEventListener('mouseup', handleEnd);
document.addEventListener('touchend', handleEnd);
}
if (sliderConfig.enableAutoplay) {
startAutoplay();
}
function startAutoplay() {
if (autoplayInterval) {
clearInterval(autoplayInterval);
}
autoplayInterval = setInterval(() => {
let nextSlide = currentSlide + effectiveAdvanceStep;
// Control del bucle infinito en autoplay
if (effectiveSlidesPerView > 1 && slidesCount >= effectiveSlidesPerView) {
if (nextSlide >= slidesCount + (effectiveSlidesPerView - 1)) {
nextSlide = (effectiveSlidesPerView - 1) + effectiveAdvanceStep;
slidesWrapper.style.transition = 'none';
goToSlideInstant(nextSlide);
setTimeout(() => {
slidesWrapper.style.transition = `transform ${sliderConfig.transitionSpeed}s cubic-bezier(0.25, 0.8, 0.25, 1)`;
goToSlide(nextSlide);
}, 50);
return;
}
} else {
if (nextSlide >= slidesCount) {
nextSlide = 0;
}
}
goToSlide(nextSlide);
}, sliderConfig.autoplayInterval * 1000);
}
sliderContainer.addEventListener('mouseenter', () => {
if (autoplayInterval) {
clearInterval(autoplayInterval);
}
});
sliderContainer.addEventListener('mouseleave', () => {
if (sliderConfig.enableAutoplay && !isDragging) {
startAutoplay();
}
});
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initSlider);
} else {
initSlider();
}
window.addEventListener('load', initSlider);
const observer = new MutationObserver((mutations) => {
let shouldInit = false;
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1) {
if (node.hasAttribute && node.hasAttribute('data-slider')) {
shouldInit = true;
} else if (node.querySelectorAll) {
const sliderElements = node.querySelectorAll('[data-slider]');
if (sliderElements.length > 0) {
shouldInit = true;
}
}
}
});
} else if (mutation.type === 'attributes' && mutation.attributeName === 'data-slider') {
shouldInit = true;
}
});
if (shouldInit) {
setTimeout(initSlider, 100);
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['data-slider', 'data-slide']
});
})();