1/** 2 * Newsletter Popup â vanilla JS, no jQuery 3 * @author Ecoeureka 4 */ 5(function () { 6 'use strict'; 7 8 var cfg = window.__npp; 9 if (!cfg) return; 10 11 /* El nombre incluye la versión del popup: al subirla desde el BO, las cookies anteriores dejan de contar. 12 El PHP lee la misma cookie y, si existe, no pinta el popup. */ 13 var COOKIE_NAME = cfg.cookieName || 'npp_v1'; 14 var HIDE_DAYS = typeof cfg.hideDays === 'number' ? cfg.hideDays : 30; 15 var SUBSCRIBED_DAYS = cfg.subscribedDays || 365; 16 17 /* ââ Helpers âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 18 19 function isMobile() { 20 return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( 21 navigator.userAgent 22 ); 23 } 24 25 function hasCookie() { 26 return document.cookie.split('; ').some(function (c) { 27 return c.indexOf(COOKIE_NAME + '=') === 0; 28 }); 29 } 30 31 /* days = 0 â cookie de sesión */ 32 function setCookie(value, days) { 33 var cookie = COOKIE_NAME + '=' + value + '; path=/; SameSite=Lax'; 34 if (days > 0) cookie += '; max-age=' + Math.round(days * 86400); 35 if (location.protocol === 'https:') cookie += '; Secure'; 36 document.cookie = cookie; 37 } 38 39 /* La comprobación en JS cubre páginas servidas desde caché, donde el PHP no ha visto la cookie */ 40 function shouldShow() { 41 return !hasCookie(); 42 } 43 44 /** 45 * @param {boolean} subscribed true â suscrito (SUBSCRIBED_DAYS), false â cerrado con la X (HIDE_DAYS) 46 */ 47 var isSubscribed = false; 48 49 function markShown(subscribed) { 50 if (isSubscribed) return; /* no sustituir la cookie de suscripción por la de cierre */ 51 52 if (subscribed) { 53 isSubscribed = true; 54 setCookie('subscribed', SUBSCRIBED_DAYS); 55 } else { 56 setCookie('closed', HIDE_DAYS); 57 } 58 } 59 60 function esc(str) { 61 var d = document.createElement('div'); 62 d.appendChild(document.createTextNode(String(str))); 63 return d.innerHTML; 64 } 65 66 /* Posición efectiva: bottom_right solo en desktop */ 67 function effectivePosition() { 68 return isMobile() ? 'center' : (cfg.position || 'center'); 69 } 70 71 /* ââ DOM builder âââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 72 73 function buildPopup() { 74 var i18n = cfg.i18n || {}; 75 var pos = effectivePosition(); 76 77 var overlay = document.createElement('div'); 78 overlay.id = 'npp-overlay'; 79 overlay.classList.add('npp-pos-' + pos.replace('_', '-')); 80 overlay.setAttribute('role', 'dialog'); 81 overlay.setAttribute('aria-modal', 'true'); 82 83 var boxStyle = 'background-color:' + esc(cfg.bgColor || '#ffffff') + ';'; 84 if (cfg.maxWidth) boxStyle += 'max-width:' + parseInt(cfg.maxWidth, 10) + 'px;'; 85 86 var headerHtml = cfg.headerImage 87 ? '<div class="npp-header-img"><img src="' + esc(cfg.headerImage) + '" alt="" role="presentation"></div>' 88 : ''; 89 90 /* El iframe se crea al abrir el popup: no se hace ninguna petición a Brevo hasta entonces */ 91 var iframeHtml = cfg.iframeUrl 92 ? '<div class="npp-iframe">' + 93 '<iframe src="' + esc(cfg.iframeUrl) + '"' + 94 ' height="' + (parseInt(cfg.iframeHeight, 10) || 400) + '"' + 95 ' frameborder="0" scrolling="auto" allowfullscreen></iframe>' + 96 '</div>' 97 : ''; 98 99 overlay.innerHTML = 100 '<div id="npp-box" style="' + boxStyle + '">' + 101 '<button type="button" id="npp-close" aria-label="' + esc(i18n.close || 'Cerrar') + '">' + 102 '<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" focusable="false">' + 103 '<path d="M5 5l14 14M19 5L5 19" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none"/>' + 104 '</svg>' + 105 '</button>' + 106 headerHtml + 107 (cfg.content ? '<div id="npp-content">' + cfg.content + '</div>' : '') + 108 (cfg.formHtml ? '<div class="npp-form"></div>' : '') + 109 iframeHtml + 110 '</div>'; 111 112 return overlay; 113 } 114 115 /* ââ Formulario HTML (código de Brevo) ââââââââââââââââââââââââââââââââ */ 116 117 var SUCCESS_SELECTOR = '#success-message.sib-form-message-panel--active'; 118 var SUCCESS_CLOSE_MS = 5000; 119 120 /* Los <script> metidos con innerHTML no se ejecutan: se recrean uno a uno y en orden, 121 esperando a que cargue cada script externo antes de seguir (main.js necesita las 122 variables window.* del script inline y el formulario ya en el DOM). */ 123 function runScripts(scripts, i) { 124 if (i >= scripts.length) return; 125 126 var old = scripts[i]; 127 var el = document.createElement('script'); 128 129 for (var a = 0; a < old.attributes.length; a++) { 130 el.setAttribute(old.attributes[a].name, old.attributes[a].value); 131 } 132 133 if (old.src) { 134 el.onload = el.onerror = function () { runScripts(scripts, i + 1); }; 135 document.body.appendChild(el); 136 } else { 137 el.textContent = old.textContent; 138 document.body.appendChild(el); 139 runScripts(scripts, i + 1); 140 } 141 } 142 143 function mountForm(container) { 144 var tpl = document.createElement('template'); 145 tpl.innerHTML = cfg.formHtml; 146 147 var scripts = Array.prototype.slice.call(tpl.content.querySelectorAll('script'));
148 scripts.forEach(function (sc) { sc.parentNode.removeChild(sc); }); 149 150 container.appendChild(tpl.content); 151 watchSuccess(container); 152 runScripts(scripts, 0); 153 } 154 155 /* Brevo marca el éxito añadiendo una clase a #success-message */ 156 function watchSuccess(container) { 157 if (!window.MutationObserver) return; 158 159 var observer = new MutationObserver(function () { 160 if (!container.querySelector(SUCCESS_SELECTOR)) return; 161 observer.disconnect(); 162 markShown(true); 163 setTimeout(function () { closePopup(false); }, SUCCESS_CLOSE_MS); 164 }); 165 166 observer.observe(container, { attributes: true, attributeFilter: ['class'], subtree: true }); 167 } 168 169 /* ââ Lifecycle âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 170 171 /* El popup se crea la primera vez que se abre y después solo se oculta: 172 asà el formulario de Brevo no se reinicializa si se vuelve a abrir con nppOpen() */ 173 var popupEl = null; 174 var isOpen = false; 175 176 function createPopup() { 177 popupEl = buildPopup(); 178 document.body.appendChild(popupEl); 179 180 document.getElementById('npp-close').addEventListener('click', function () { 181 closePopup(true); /* X â marcar y no volver a mostrar */ 182 }); 183 184 /* Clic en overlay: cierra visualmente pero NO marca â el popup vuelve en la siguiente página */ 185 popupEl.addEventListener('click', function (e) { 186 if (e.target === popupEl) closePopup(false); 187 }); 188 189 var formContainer = popupEl.querySelector('.npp-form'); 190 if (formContainer) mountForm(formContainer); 191 } 192 193 function openPopup() { 194 if (isOpen) return; /* ya visible */ 195 isOpen = true; 196 197 if (!popupEl) createPopup(); 198 199 /* Doble rAF para que la transición CSS dispare desde el estado inicial */ 200 requestAnimationFrame(function () { 201 requestAnimationFrame(function () { 202 popupEl.classList.add('npp-visible'); 203 }); 204 }); 205 206 /* ESC: cierre sin marcar */ 207 document.addEventListener('keydown', onKeyDown); 208 } 209 210 function showPopup() { 211 if (shouldShow()) openPopup(); 212 } 213 214 /** 215 * @param {boolean} permanent true â guardar en storage (no volver a mostrar) 216 */ 217 function closePopup(permanent) { 218 if (!isOpen) return; 219 isOpen = false; 220 221 if (permanent) markShown(false); 222 223 document.removeEventListener('keydown', onKeyDown); 224 popupEl.classList.remove('npp-visible'); 225 } 226 227 function onKeyDown(e) { 228 if (e.key === 'Escape') closePopup(false); 229 } 230 231 /* ââ Triggers ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 232 233 function setupTriggers() { 234 if (isMobile()) { 235 var target = (cfg.triggerMobile || 50) / 100; 236 237 function onScroll() { 238 var scrollable = document.body.scrollHeight - window.innerHeight; 239 if (scrollable <= 0) return; 240 if (window.scrollY / scrollable >= target) { 241 window.removeEventListener('scroll', onScroll); 242 showPopup(); 243 } 244 } 245 window.addEventListener('scroll', onScroll, { passive: true }); 246 } else { 247 setTimeout(showPopup, (cfg.triggerDesktop || 0) * 1000); 248 } 249 } 250 251 /* ââ Entry âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 252 253 if (document.readyState === 'loading') { 254 document.addEventListener('DOMContentLoaded', setupTriggers); 255 } else { 256 setupTriggers(); 257 } 258 259 /* ââ API pública âââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 260 /* Permite abrir el popup desde cualquier botón/enlace de la página: 261 <button onclick="nppOpen()">Suscribirse</button> */ 262 window.nppOpen = openPopup; 263})();
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.