diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 77b7f9a2..b30bc63e 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -4,7 +4,11 @@ "Bash(mkdir:*)", "mcp__serena__activate_project", "mcp__serena__find_symbol", - "Bash(ssh:*)" + "Bash(ssh:*)", + "Bash(php:*)", + "mcp__playwright__browser_console_messages", + "mcp__playwright__browser_evaluate", + "mcp__playwright__browser_navigate" ] } } diff --git a/Public/AdsensePlacement/Infrastructure/Ui/AdsensePlacementRenderer.php b/Public/AdsensePlacement/Infrastructure/Ui/AdsensePlacementRenderer.php index 6c692b0d..ba05bee1 100644 --- a/Public/AdsensePlacement/Infrastructure/Ui/AdsensePlacementRenderer.php +++ b/Public/AdsensePlacement/Infrastructure/Ui/AdsensePlacementRenderer.php @@ -66,36 +66,55 @@ final class AdsensePlacementRenderer // 4. Generar CSS (usando CSSGeneratorService) $lazyEnabled = ($settings['behavior']['lazy_loading_enabled'] ?? true) === true; - // Nota: Para lazy loading usamos min-height:1px para que IntersectionObserver - // pueda detectar el elemento. Con display:none o height:0, IO no puede observar - // elementos porque tienen dimensiones 0x0 en el DOM. - // El contenido se oculta con visibility:hidden hasta que se active el slot. - $css = $this->cssGenerator->generate( - ".roi-ad-slot", - [ - 'visibility' => $lazyEnabled ? 'hidden' : 'visible', - 'min_height' => $lazyEnabled ? '1px' : 'auto', - 'width' => '100%', - 'min_width' => '300px', + // Estrategia para evitar layout shift: + // - Los slots inician colapsados (max-height:0) sin ocupar espacio visual + // - Usamos max-height en vez de height para que IO pueda detectarlos + // - Se pre-cargan con rootMargin grande (1000px) antes de ser visibles + // - Solo se expanden cuando AdSense confirma que hay anuncio (filled) + // - Si no hay anuncio (unfilled), permanecen colapsados + // - Esto evita que el usuario vea espacios que luego desaparecen + if ($lazyEnabled) { + $css = $this->cssGenerator->generate( + ".roi-ad-slot", + [ + 'max_height' => '0', + 'overflow' => 'hidden', + 'opacity' => '0', + 'width' => '100%', + 'min_width' => '300px', + 'text_align' => 'center', + 'margin' => '0', + 'padding' => '0', + 'transition' => 'max-height 0.3s ease, opacity 0.3s ease, margin 0.3s ease', + ] + ); + // Slots con anuncio confirmado: se expanden y muestran + $css .= $this->cssGenerator->generate('.roi-ad-slot.roi-ad-filled', [ + 'max_height' => '600px', + 'opacity' => '1', + 'overflow' => 'visible', 'margin_top' => '1.5rem', 'margin_bottom' => '1.5rem', - 'text_align' => 'center', - ] - ); - - // CSS para slots con lazy loading que reciben contenido - if ($lazyEnabled) { - $css .= $this->cssGenerator->generate('.roi-ad-slot.roi-ad-filled', [ - 'visibility' => 'visible', - 'min_height' => 'auto', ]); + // Slots vacios: permanecen colapsados sin ocupar espacio $css .= $this->cssGenerator->generate('.roi-ad-slot.roi-ad-empty', [ - 'visibility' => 'hidden', - 'min_height' => '0', - 'height' => '0', + 'max_height' => '0', + 'opacity' => '0', 'margin' => '0', 'padding' => '0', ]); + } else { + // Modo legacy: slots siempre visibles + $css = $this->cssGenerator->generate( + ".roi-ad-slot", + [ + 'width' => '100%', + 'min_width' => '300px', + 'margin_top' => '1.5rem', + 'margin_bottom' => '1.5rem', + 'text_align' => 'center', + ] + ); } // 5. Generar HTML del anuncio diff --git a/functions.php b/functions.php index af3c071f..78ecb670 100644 --- a/functions.php +++ b/functions.php @@ -18,7 +18,7 @@ if (!defined('ABSPATH')) { } // Definir constante de versión del tema -define('ROI_VERSION', '1.0.24'); +define('ROI_VERSION', '1.0.25'); // ============================================================================= // 1. CARGAR AUTOLOADER MANUAL