- Anchor Ads: Cambiar de visibility:hidden a transform:translateY() para que AdSense pueda medir dimensiones del slot - Vignette Ads: Solo mostrar overlay cuando data-ad-status="filled" - Mover card Exclusiones a columna izquierda en admin 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
455 lines
14 KiB
JavaScript
455 lines
14 KiB
JavaScript
/**
|
|
* ROI Theme - Anchor & Vignette Ads JavaScript
|
|
*
|
|
* Logica para:
|
|
* - Mostrar/ocultar/colapsar anchors
|
|
* - Triggers de vignette (pageview, scroll, exit_intent, time_delay)
|
|
* - Persistencia con localStorage
|
|
* - Tiempo de reaparicion configurable
|
|
*
|
|
* NO usa onclick inline - usa addEventListener
|
|
*/
|
|
(function() {
|
|
'use strict';
|
|
|
|
// =====================================================
|
|
// UTILIDADES
|
|
// =====================================================
|
|
|
|
/**
|
|
* Obtiene configuracion desde script type="application/json"
|
|
*/
|
|
function getConfig(id) {
|
|
var el = document.getElementById(id);
|
|
if (!el) return null;
|
|
try {
|
|
return JSON.parse(el.textContent);
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calcula duracion en milisegundos
|
|
*/
|
|
function getDurationMs(duration) {
|
|
switch (duration) {
|
|
case 'session': return 0; // sessionStorage
|
|
case '1hour': return 60 * 60 * 1000;
|
|
case '1day': return 24 * 60 * 60 * 1000;
|
|
case '1week': return 7 * 24 * 60 * 60 * 1000;
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Guarda estado en storage
|
|
*/
|
|
function saveState(key, value, duration) {
|
|
var data = {
|
|
value: value,
|
|
expires: duration === 'session' ? 0 : Date.now() + getDurationMs(duration)
|
|
};
|
|
|
|
if (duration === 'session') {
|
|
sessionStorage.setItem(key, JSON.stringify(data));
|
|
} else {
|
|
localStorage.setItem(key, JSON.stringify(data));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recupera estado desde storage
|
|
*/
|
|
function getState(key, duration) {
|
|
var storage = duration === 'session' ? sessionStorage : localStorage;
|
|
var raw = storage.getItem(key);
|
|
|
|
if (!raw) return null;
|
|
|
|
try {
|
|
var data = JSON.parse(raw);
|
|
|
|
// Verificar expiracion
|
|
if (data.expires && data.expires < Date.now()) {
|
|
storage.removeItem(key);
|
|
return null;
|
|
}
|
|
|
|
return data.value;
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// =====================================================
|
|
// ANCHOR ADS
|
|
// =====================================================
|
|
|
|
/**
|
|
* Observa los slots de AdSense y MUESTRA el contenedor solo cuando se llena
|
|
* Los anchors estan OCULTOS por defecto via CSS
|
|
* AdSense agrega data-ad-status="filled" cuando hay anuncio
|
|
*/
|
|
function watchAdStatus(containers) {
|
|
containers.forEach(function(container) {
|
|
var ins = container.querySelector('ins.adsbygoogle');
|
|
if (!ins) return;
|
|
|
|
// Funcion para mostrar el anchor cuando el ad esta listo
|
|
function showIfFilled() {
|
|
var status = ins.getAttribute('data-ad-status');
|
|
if (status === 'filled') {
|
|
container.classList.add('ad-loaded');
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Verificar si ya tiene estado (por si ya cargo)
|
|
if (showIfFilled()) {
|
|
return;
|
|
}
|
|
|
|
// Usar MutationObserver para detectar cuando AdSense llena el slot
|
|
var observer = new MutationObserver(function(mutations) {
|
|
mutations.forEach(function(mutation) {
|
|
if (mutation.type === 'attributes' && mutation.attributeName === 'data-ad-status') {
|
|
if (showIfFilled()) {
|
|
observer.disconnect();
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
observer.observe(ins, {
|
|
attributes: true,
|
|
attributeFilter: ['data-ad-status']
|
|
});
|
|
|
|
// Timeout de seguridad: despues de 5s desconectar observer
|
|
// Si no se lleno, el anchor permanece oculto (comportamiento deseado)
|
|
setTimeout(function() {
|
|
observer.disconnect();
|
|
}, 5000);
|
|
});
|
|
}
|
|
|
|
function initAnchorAds() {
|
|
var config = getConfig('roi-anchor-config');
|
|
if (!config) return;
|
|
|
|
var anchors = document.querySelectorAll('.roi-anchor-ad');
|
|
if (!anchors.length) return;
|
|
|
|
// Restaurar estados guardados
|
|
anchors.forEach(function(anchor) {
|
|
var pos = anchor.dataset.position;
|
|
var stateKey = 'roi_anchor_' + pos + '_state';
|
|
|
|
if (config.rememberState) {
|
|
var savedState = getState(stateKey, config.rememberDuration);
|
|
|
|
if (savedState === 'closed') {
|
|
anchor.classList.add('hidden');
|
|
} else if (savedState === 'collapsed' && config.collapsible) {
|
|
anchor.classList.add('collapsed');
|
|
}
|
|
}
|
|
});
|
|
|
|
// Mostrar anchors solo cuando AdSense llene el slot (ocultos por defecto via CSS)
|
|
watchAdStatus(anchors);
|
|
|
|
// Event delegation para botones
|
|
document.addEventListener('click', function(e) {
|
|
var btn = e.target.closest('[data-action]');
|
|
if (!btn) return;
|
|
|
|
var action = btn.dataset.action;
|
|
var anchor = btn.closest('.roi-anchor-ad');
|
|
|
|
if (!anchor) return;
|
|
|
|
var pos = anchor.dataset.position;
|
|
var stateKey = 'roi_anchor_' + pos + '_state';
|
|
|
|
switch (action) {
|
|
case 'close':
|
|
anchor.classList.add('hidden');
|
|
if (config.rememberState) {
|
|
saveState(stateKey, 'closed', config.rememberDuration);
|
|
}
|
|
break;
|
|
|
|
case 'collapse':
|
|
if (config.collapsible) {
|
|
anchor.classList.add('collapsed');
|
|
if (config.rememberState) {
|
|
saveState(stateKey, 'collapsed', config.rememberDuration);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'expand':
|
|
anchor.classList.remove('collapsed');
|
|
if (config.rememberState) {
|
|
saveState(stateKey, 'expanded', config.rememberDuration);
|
|
}
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
|
|
// =====================================================
|
|
// VIGNETTE ADS
|
|
// =====================================================
|
|
|
|
function initVignetteAds() {
|
|
var config = getConfig('roi-vignette-config');
|
|
if (!config) return;
|
|
|
|
var overlay = document.getElementById('roi-vignette-overlay');
|
|
if (!overlay) return;
|
|
|
|
var STORAGE_KEYS = {
|
|
lastClosed: 'roi_vignette_last_closed',
|
|
sessionCount: 'roi_vignette_session_count',
|
|
pageCount: 'roi_vignette_page_count_' + window.location.pathname
|
|
};
|
|
|
|
// Estado local
|
|
var pageShowCount = 0;
|
|
var triggered = false;
|
|
|
|
/**
|
|
* Verifica si se puede mostrar el vignette
|
|
*/
|
|
function canShow() {
|
|
// Verificar max por pagina
|
|
var maxPage = config.maxPerPage === 'unlimited' ? 999 : parseInt(config.maxPerPage);
|
|
if (pageShowCount >= maxPage) return false;
|
|
|
|
// Verificar max por sesion
|
|
var maxSession = config.maxPerSession === 'unlimited' ? 999 : parseInt(config.maxPerSession);
|
|
var sessionCount = parseInt(sessionStorage.getItem(STORAGE_KEYS.sessionCount) || '0');
|
|
if (sessionCount >= maxSession) return false;
|
|
|
|
// Verificar tiempo de reaparicion
|
|
if (config.reshowEnabled) {
|
|
var lastClosed = parseInt(localStorage.getItem(STORAGE_KEYS.lastClosed) || '0');
|
|
var minWait = config.reshowTime * 60 * 1000; // minutos a ms
|
|
|
|
if (lastClosed && (Date.now() - lastClosed) < minWait) {
|
|
return false;
|
|
}
|
|
} else {
|
|
// Si no se permite reaparicion, verificar si ya se cerro
|
|
if (sessionStorage.getItem(STORAGE_KEYS.lastClosed)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Muestra el vignette SOLO si AdSense lleno el slot
|
|
*/
|
|
function showVignette() {
|
|
if (!canShow() || triggered) return;
|
|
|
|
triggered = true;
|
|
|
|
// Buscar el elemento ins de AdSense dentro del overlay
|
|
var ins = overlay.querySelector('ins.adsbygoogle');
|
|
if (!ins) return;
|
|
|
|
// Funcion para mostrar el overlay cuando el ad esta listo
|
|
function showIfFilled() {
|
|
var status = ins.getAttribute('data-ad-status');
|
|
if (status === 'filled') {
|
|
// AdSense lleno el slot - mostrar overlay
|
|
overlay.classList.add('active');
|
|
document.body.style.overflow = 'hidden';
|
|
|
|
// Incrementar contadores
|
|
pageShowCount++;
|
|
var sessionCount = parseInt(sessionStorage.getItem(STORAGE_KEYS.sessionCount) || '0');
|
|
sessionStorage.setItem(STORAGE_KEYS.sessionCount, (sessionCount + 1).toString());
|
|
return true;
|
|
} else if (status === 'unfilled') {
|
|
// AdSense no tiene anuncio - no mostrar nada
|
|
triggered = false; // Permitir reintento mas tarde
|
|
return true; // Detener observacion
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Verificar si ya tiene estado
|
|
if (showIfFilled()) {
|
|
return;
|
|
}
|
|
|
|
// Observar cambios en data-ad-status
|
|
var observer = new MutationObserver(function(mutations) {
|
|
mutations.forEach(function(mutation) {
|
|
if (mutation.type === 'attributes' && mutation.attributeName === 'data-ad-status') {
|
|
if (showIfFilled()) {
|
|
observer.disconnect();
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
observer.observe(ins, {
|
|
attributes: true,
|
|
attributeFilter: ['data-ad-status']
|
|
});
|
|
|
|
// Timeout de seguridad: despues de 5s desconectar observer
|
|
setTimeout(function() {
|
|
observer.disconnect();
|
|
// Si no se lleno, permitir reintento
|
|
var status = ins.getAttribute('data-ad-status');
|
|
if (status !== 'filled') {
|
|
triggered = false;
|
|
}
|
|
}, 5000);
|
|
}
|
|
|
|
/**
|
|
* Cierra el vignette
|
|
*/
|
|
function closeVignette() {
|
|
overlay.classList.remove('active');
|
|
document.body.style.overflow = '';
|
|
|
|
// Guardar tiempo de cierre
|
|
localStorage.setItem(STORAGE_KEYS.lastClosed, Date.now().toString());
|
|
|
|
if (!config.reshowEnabled) {
|
|
sessionStorage.setItem(STORAGE_KEYS.lastClosed, '1');
|
|
}
|
|
|
|
// Permitir nueva trigger si se permite reaparicion
|
|
if (config.reshowEnabled) {
|
|
triggered = false;
|
|
}
|
|
}
|
|
|
|
// Event listeners para cerrar
|
|
document.addEventListener('click', function(e) {
|
|
var btn = e.target.closest('[data-action="close-vignette"]');
|
|
if (btn) {
|
|
closeVignette();
|
|
return;
|
|
}
|
|
|
|
// Click fuera del modal
|
|
if (e.target === overlay) {
|
|
closeVignette();
|
|
}
|
|
});
|
|
|
|
// Escape key
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Escape' && overlay.classList.contains('active')) {
|
|
closeVignette();
|
|
}
|
|
});
|
|
|
|
// === TRIGGERS ===
|
|
|
|
/**
|
|
* Trigger: Al cargar pagina
|
|
*/
|
|
function setupPageviewTrigger() {
|
|
setTimeout(function() {
|
|
showVignette();
|
|
}, config.triggerDelay * 1000);
|
|
}
|
|
|
|
/**
|
|
* Trigger: Al scrollear X%
|
|
*/
|
|
function setupScrollTrigger(percent) {
|
|
var scrollHandler = function() {
|
|
if (triggered) return;
|
|
|
|
var scrollHeight = document.documentElement.scrollHeight - window.innerHeight;
|
|
var scrolled = window.scrollY / scrollHeight;
|
|
|
|
if (scrolled >= percent / 100) {
|
|
showVignette();
|
|
window.removeEventListener('scroll', scrollHandler);
|
|
}
|
|
};
|
|
|
|
window.addEventListener('scroll', scrollHandler, { passive: true });
|
|
}
|
|
|
|
/**
|
|
* Trigger: Exit intent (mouse sale del viewport)
|
|
*/
|
|
function setupExitIntentTrigger() {
|
|
var exitHandler = function(e) {
|
|
if (triggered) return;
|
|
|
|
// Solo activar si el mouse sale por arriba
|
|
if (e.clientY <= 0) {
|
|
showVignette();
|
|
document.removeEventListener('mouseout', exitHandler);
|
|
}
|
|
};
|
|
|
|
document.addEventListener('mouseout', exitHandler);
|
|
}
|
|
|
|
/**
|
|
* Trigger: Despues de X segundos
|
|
*/
|
|
function setupTimeDelayTrigger() {
|
|
setTimeout(function() {
|
|
showVignette();
|
|
}, config.triggerDelay * 1000);
|
|
}
|
|
|
|
// Configurar trigger segun config
|
|
switch (config.trigger) {
|
|
case 'pageview':
|
|
setupPageviewTrigger();
|
|
break;
|
|
case 'scroll_50':
|
|
setupScrollTrigger(50);
|
|
break;
|
|
case 'scroll_75':
|
|
setupScrollTrigger(75);
|
|
break;
|
|
case 'exit_intent':
|
|
setupExitIntentTrigger();
|
|
break;
|
|
case 'time_delay':
|
|
setupTimeDelayTrigger();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// =====================================================
|
|
// INICIALIZACION
|
|
// =====================================================
|
|
|
|
function init() {
|
|
initAnchorAds();
|
|
initVignetteAds();
|
|
}
|
|
|
|
// Ejecutar cuando el DOM este listo
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
} else {
|
|
init();
|
|
}
|
|
|
|
})();
|