<template id="popup-template">
<div class="c-popup" aria-hidden="true">
<div class="c-popup__overlay"></div>
<div class="c-popup__box">
<button class="c-popup__close" data-action="close">×</button>
<h2 class="c-popup__title"></h2>
<div class="c-popup__content"></div>
<label class="c-popup__remember hidden">
<input type="checkbox">
No volver a mostrar
</label>
<div class="c-popup__actions">
<button class="btn-g borde c-btn--cancel" data-action="cancel"></button>
<button class="btn-g borde c-btn--confirm" data-action="confirm"></button>
</div>
</div>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/swiper/swiper-bundle.min.js"></script>
<script>
/* =====================================================
POPUP GENÉRICO REUTILIZABLE
===================================================== */
class Popup {
constructor(options = {}) {
this.options = options;
// Evitar mostrar si está recordado
if (options.rememberKey && localStorage.getItem(options.rememberKey)) {
return;
}
this.template = document.getElementById('popup-template');
if (!this.template) {
console.error('Popup template no encontrado');
return;
}
this.popup = this.template.content.firstElementChild.cloneNode(true);
this.cache();
this.bind();
this.render();
}
/* -----------------------------
Cachear elementos
------------------------------ */
cache() {
this.overlay = this.popup.querySelector('.c-popup__overlay');
this.titleEl = this.popup.querySelector('.c-popup__title');
this.contentEl = this.popup.querySelector('.c-popup__content');
this.btnConfirm = this.popup.querySelector('[data-action="confirm"]');
this.btnCancel = this.popup.querySelector('[data-action="cancel"]');
this.btnClose = this.popup.querySelector('[data-action="close"]');
this.rememberWrap = this.popup.querySelector('.c-popup__remember');
this.rememberInput = this.rememberWrap.querySelector('input');
}
/* -----------------------------
Eventos
------------------------------ */
bind() {
this.overlay.addEventListener('click', () => this.close());
this.btnClose.addEventListener('click', () => this.close());
this.btnConfirm.addEventListener('click', () => this.confirm());
this.btnCancel.addEventListener('click', () => this.cancel());
document.addEventListener('keydown', this.handleEsc);
}
handleEsc = (e) => {
if (e.key === 'Escape') {
this.close();
}
}
/* -----------------------------
Render
------------------------------ */
render() {
const o = this.options;
this.titleEl.textContent = o.title || '';
this.contentEl.innerHTML = this.buildContent(o);
/*this.btnConfirm.textContent = o.confirmText || 'Continuar';
this.btnCancel.textContent = o.cancelText || 'Cancelar';*/
const actionsWrap = this.popup.querySelector('.c-popup__actions');
console.log('actionsWrap', actionsWrap);
/* -------- CONFIRM -------- */
if (o.confirmText) {
this.btnConfirm.textContent = o.confirmText;
this.btnConfirm.classList.remove('hidden');
} else {
this.btnConfirm.classList.add('hidden');
}
/* -------- CANCEL -------- */
if (o.cancelText) {
this.btnCancel.textContent = o.cancelText;
this.btnCancel.classList.remove('hidden');
} else {
this.btnCancel.classList.add('hidden');
}
/* -------- CONTENEDOR -------- */
const hasAnyAction = !!(o.confirmText || o.cancelText);
actionsWrap.classList.toggle('hidden', !hasAnyAction);
this.rememberWrap.classList.toggle('hidden', !o.rememberKey);
document.body.appendChild(this.popup);
requestAnimationFrame(() => {
this.popup.classList.add('is-open');
if (o.type === 'gallery') {
// Espera un frame más para que el layout exista
console.log('llama swiper');
requestAnimationFrame(() => {
this.initGallerySwiper();
});
}
});
}
/* -----------------------------
Contenido dinámico
------------------------------ */
buildContent(o) {
switch (o.type) {
case 'image':
return `<img src="${o.src}" alt="">`;
case 'gallery':
return `
<div class="gallery-content">
<div class="swiper-gallery-property swiper">
<div class="swiper-wrapper">
${o.images.map(src => `
<div class="swiper-slide">
<div class="swiper-wp">
<img src="${src}"/>
</div>
</div>`).join('')}
</div>
</div>
</div>
<div class="gallery-content-thumbnails">
<div thumbsSlider="" class="swiper swiper-thumbs-gallery-detail-property">
<div class="swiper-wrapper">
${o.images.map(src => `
<div class="swiper-slide">
<div class="gallery-content-thumbnails-item">
<img src="${src}"/>
</div>
</div>`).join('')}
</div>
</div>
</div>
`;
case 'video':
return `
<video controls>
<source src="${o.src}">
</video>
`;
case 'html':
return o.html;
case 'message':
default:
return `<p>${o.message || ''}</p>`;
}
}
/* -----------------------------
Acciones
------------------------------ */
confirm() {
if (this.rememberInput.checked && this.options.rememberKey) {
localStorage.setItem(this.options.rememberKey, '1');
}
this.options.onConfirm?.();
this.close();
}
cancel() {
this.options.onCancel?.();
this.close();
}
close() {
this.popup.classList.remove('is-open');
document.removeEventListener('keydown', this.handleEsc);
if (this.mainSwiper) {
this.mainSwiper.destroy(true, true);
}
this.popup.addEventListener('transitionend', () => {
this.popup.remove();
}, { once: true });
}
initGallerySwiper() {
const mainEl = this.popup.querySelector('.swiper-gallery-property');
const thumbsEl = this.popup.querySelector('.swiper-thumbs-gallery-detail-property');
console.log('swiper', mainEl);
if (!mainEl || !thumbsEl || typeof Swiper === 'undefined') return;
const thumbsSwiper = new Swiper(thumbsEl, {
slidesPerView: 5,
spaceBetween: 5,
watchSlidesProgress: true,
freeMode: true,
});
this.mainSwiper = new Swiper(mainEl, {
spaceBetween: 10,
slidesPerView: 1,
thumbs: {
swiper: thumbsSwiper,
},
});
}
}
/* =====================================================
LISTENER GLOBAL PARA BOTONES
===================================================== */
document.addEventListener('click', function (e) {
const btn = e.target.closest('.call-popup');
if (!btn) return;
e.preventDefault();
const type = btn.dataset.type;
if (!type) return;
const options = {
type,
title: btn.dataset.title || '',
confirmText: btn.dataset.confirmText || null,
cancelText: btn.dataset.cancelText || null,
rememberKey: btn.dataset.remember || null
};
/* ---------- GALERÍA ---------- */
if (type === 'gallery') {
const galleryId = btn.dataset.gallery;
const galleryEl = document.getElementById(galleryId);
if (!galleryEl) return;
options.images = [...galleryEl.querySelectorAll('img')]
.map(img => img.src);
}
/* ---------- IMAGEN ---------- */
if (type === 'image') {
options.src = btn.dataset.src;
}
/* ---------- VIDEO ---------- */
if (type === 'video') {
options.src = btn.dataset.src;
}
/* ---------- HTML ---------- */
if (type === 'html') {
const htmlId = btn.dataset.html;
const htmlEl = document.getElementById(htmlId);
options.html = htmlEl ? htmlEl.innerHTML : '';
}
/* ---------- MENSAJE ---------- */
if (type === 'message') {
options.message = btn.dataset.message || '';
}
new Popup(options);
});
</script>