fix: Container width setting now applies correctly + Rail Ads improvements
- Fix container width not applying: css-global-responsive.css now uses CSS variable --roi-container-width instead of hardcoded values - Add 8 Rail format options: slim-small (160x300), slim-medium (160x400), slim-large (160x500), skyscraper (160x600), slim-xlarge (160x700), wide-skyscraper (160x800), half-page (300x600), large-skyscraper (300x1050) - Change rail_top_offset from text input to select with preset values - Fix Rail Ads JavaScript positioning (moved after HTML, added retries) - ThemeSettingsRenderer now always outputs CSS variables for layout 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -462,16 +462,35 @@ final class AdsensePlacementFormBuilder
|
|||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
$html .= '</div>';
|
$html .= '</div>';
|
||||||
|
|
||||||
// Format select
|
// Format select - Opciones de altura para anuncios verticales
|
||||||
$railFormat = $this->renderer->getFieldValue($cid, 'behavior', 'rail_format', 'skyscraper');
|
$railFormat = $this->renderer->getFieldValue($cid, 'behavior', 'rail_format', 'skyscraper');
|
||||||
$html .= $this->buildSelect($cid . 'RailFormat', 'Formato',
|
$html .= $this->buildSelect($cid . 'RailFormat', 'Formato',
|
||||||
$railFormat,
|
$railFormat,
|
||||||
['skyscraper' => 'Skyscraper (160x600)', 'half-page' => 'Half Page (300x600)']
|
[
|
||||||
|
'slim-small' => 'Slim Small (160x300)',
|
||||||
|
'slim-medium' => 'Slim Medium (160x400)',
|
||||||
|
'slim-large' => 'Slim Large (160x500)',
|
||||||
|
'skyscraper' => 'Skyscraper (160x600)',
|
||||||
|
'slim-xlarge' => 'Slim XLarge (160x700)',
|
||||||
|
'wide-skyscraper' => 'Wide Skyscraper (160x800)',
|
||||||
|
'half-page' => 'Half Page (300x600)',
|
||||||
|
'large-skyscraper' => 'Large Skyscraper (300x1050)'
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Top offset
|
// Top offset - Select con opciones predefinidas
|
||||||
$topOffset = $this->renderer->getFieldValue($cid, 'behavior', 'rail_top_offset', '150');
|
$topOffset = $this->renderer->getFieldValue($cid, 'behavior', 'rail_top_offset', '300');
|
||||||
$html .= $this->buildTextInput($cid . 'RailTopOffset', 'Distancia desde arriba (px)', $topOffset);
|
$html .= $this->buildSelect($cid . 'RailTopOffset', 'Distancia desde arriba',
|
||||||
|
$topOffset,
|
||||||
|
[
|
||||||
|
'150' => '150px (Cerca del header)',
|
||||||
|
'200' => '200px',
|
||||||
|
'300' => '300px (Recomendado)',
|
||||||
|
'400' => '400px',
|
||||||
|
'500' => '500px',
|
||||||
|
'700' => '700px (Debajo del fold)'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
$html .= ' </div>';
|
$html .= ' </div>';
|
||||||
$html .= '</div>';
|
$html .= '</div>';
|
||||||
|
|||||||
@@ -246,31 +246,12 @@
|
|||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
/* Container width uses CSS variable from Theme Settings */
|
||||||
max-width: 1140px;
|
.container,
|
||||||
}
|
.container-lg,
|
||||||
|
.container-xl,
|
||||||
.container-lg {
|
|
||||||
max-width: 1280px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container-xl {
|
|
||||||
max-width: 1400px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* XXL devices (1400px and up) */
|
|
||||||
@media (min-width: 1400px) {
|
|
||||||
.container {
|
|
||||||
max-width: 1320px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container-xl {
|
|
||||||
max-width: 1500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container-xxl {
|
.container-xxl {
|
||||||
max-width: 1700px;
|
max-width: var(--roi-container-width, 1320px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -295,8 +295,12 @@ final class AdsensePlacementRenderer
|
|||||||
$delayEnabled = ($settings['forms']['delay_enabled'] ?? true) === true;
|
$delayEnabled = ($settings['forms']['delay_enabled'] ?? true) === true;
|
||||||
|
|
||||||
// Dimensiones segun formato
|
// Dimensiones segun formato
|
||||||
// skyscraper: 160x600, wide-skyscraper: 160x800, half-page: 300x600, large-skyscraper: 300x1050
|
// Opciones de 160px de ancho con diferentes alturas + opciones anchas
|
||||||
[$width, $height] = match($format) {
|
[$width, $height] = match($format) {
|
||||||
|
'slim-small' => [160, 300],
|
||||||
|
'slim-medium' => [160, 400],
|
||||||
|
'slim-large' => [160, 500],
|
||||||
|
'slim-xlarge' => [160, 700],
|
||||||
'wide-skyscraper' => [160, 800],
|
'wide-skyscraper' => [160, 800],
|
||||||
'half-page' => [300, 600],
|
'half-page' => [300, 600],
|
||||||
'large-skyscraper' => [300, 1050],
|
'large-skyscraper' => [300, 1050],
|
||||||
@@ -319,14 +323,15 @@ final class AdsensePlacementRenderer
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// Posicion rail izquierdo - usar max() para evitar valores negativos
|
// Posicion rail izquierdo - usar max() para evitar valores negativos
|
||||||
// Formula: max(10px, calc((100vw - 1320px) / 2 - (width + 20)px))
|
// Usa CSS variable --roi-container-width-numeric de ThemeSettings (fallback 1320px)
|
||||||
|
// Formula: max(10px, calc((100vw - containerWidth) / 2 - (width + 20)px))
|
||||||
$cssRules[] = $this->cssGenerator->generate('.roi-rail-ad-left', [
|
$cssRules[] = $this->cssGenerator->generate('.roi-rail-ad-left', [
|
||||||
'left' => 'max(10px, calc((100vw - 1320px) / 2 - ' . ($width + 20) . 'px))',
|
'left' => 'max(10px, calc((100vw - var(--roi-container-width-numeric, 1320px)) / 2 - ' . ($width + 20) . 'px))',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Posicion rail derecho - usar max() para consistencia
|
// Posicion rail derecho - usar max() para consistencia
|
||||||
$cssRules[] = $this->cssGenerator->generate('.roi-rail-ad-right', [
|
$cssRules[] = $this->cssGenerator->generate('.roi-rail-ad-right', [
|
||||||
'right' => 'max(10px, calc((100vw - 1320px) / 2 - ' . ($width + 20) . 'px))',
|
'right' => 'max(10px, calc((100vw - var(--roi-container-width-numeric, 1320px)) / 2 - ' . ($width + 20) . 'px))',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Media query para ocultar en pantallas < 1600px
|
// Media query para ocultar en pantallas < 1600px
|
||||||
@@ -337,94 +342,8 @@ final class AdsensePlacementRenderer
|
|||||||
|
|
||||||
$css = implode("\n", $cssRules);
|
$css = implode("\n", $cssRules);
|
||||||
|
|
||||||
// JavaScript para posicionamiento inteligente de Rail Ads
|
// HTML primero (CSS), JavaScript se añade AL FINAL
|
||||||
// Detecta dinamicamente el header/hero/featured-image y ajusta posicion
|
$html = "<style>{$css}</style>\n";
|
||||||
$js = "
|
|
||||||
<script>
|
|
||||||
(function() {
|
|
||||||
var railAds = document.querySelectorAll('.roi-rail-ad');
|
|
||||||
if (!railAds.length) return;
|
|
||||||
|
|
||||||
// Selectores especificos del tema ROI
|
|
||||||
var navbar = document.querySelector('nav.navbar');
|
|
||||||
var hero = document.querySelector('.hero-section');
|
|
||||||
var featuredImage = document.querySelector('.featured-image-container');
|
|
||||||
|
|
||||||
function getNavbarHeight() {
|
|
||||||
if (navbar) {
|
|
||||||
var style = window.getComputedStyle(navbar);
|
|
||||||
if (style.position === 'fixed' || style.position === 'sticky') {
|
|
||||||
return navbar.offsetHeight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function adjustRailPosition() {
|
|
||||||
var navHeight = getNavbarHeight();
|
|
||||||
var gap = 30; // Espacio de separacion
|
|
||||||
var newTop = navHeight + gap;
|
|
||||||
|
|
||||||
// Buscar el elemento mas bajo entre hero y featured-image
|
|
||||||
var lowestBottom = navHeight;
|
|
||||||
|
|
||||||
if (hero) {
|
|
||||||
var heroRect = hero.getBoundingClientRect();
|
|
||||||
if (heroRect.bottom > 0) {
|
|
||||||
lowestBottom = Math.max(lowestBottom, heroRect.bottom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (featuredImage) {
|
|
||||||
var featuredRect = featuredImage.getBoundingClientRect();
|
|
||||||
if (featuredRect.bottom > 0) {
|
|
||||||
lowestBottom = Math.max(lowestBottom, featuredRect.bottom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Si algo esta visible en pantalla, posicionar debajo
|
|
||||||
if (lowestBottom > navHeight) {
|
|
||||||
newTop = lowestBottom + gap;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Limitar el top maximo al 40% del viewport (mas generoso)
|
|
||||||
var maxTop = window.innerHeight * 0.4;
|
|
||||||
newTop = Math.min(newTop, maxTop);
|
|
||||||
|
|
||||||
// Asegurar minimo respetando navbar
|
|
||||||
newTop = Math.max(newTop, navHeight + gap);
|
|
||||||
|
|
||||||
railAds.forEach(function(rail) {
|
|
||||||
rail.style.top = newTop + 'px';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Throttle para mejor rendimiento
|
|
||||||
var ticking = false;
|
|
||||||
function onScroll() {
|
|
||||||
if (!ticking) {
|
|
||||||
requestAnimationFrame(function() {
|
|
||||||
adjustRailPosition();
|
|
||||||
ticking = false;
|
|
||||||
});
|
|
||||||
ticking = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('scroll', onScroll, { passive: true });
|
|
||||||
window.addEventListener('resize', adjustRailPosition, { passive: true });
|
|
||||||
|
|
||||||
// Ejecutar despues de que el DOM este listo
|
|
||||||
if (document.readyState === 'loading') {
|
|
||||||
document.addEventListener('DOMContentLoaded', adjustRailPosition);
|
|
||||||
} else {
|
|
||||||
// Esperar un poco para que carguen los elementos
|
|
||||||
setTimeout(adjustRailPosition, 100);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
</script>";
|
|
||||||
|
|
||||||
$html = "<style>{$css}</style>\n{$js}\n";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EXCEPCION DOCUMENTADA: CSS inline requerido por Google AdSense
|
* EXCEPCION DOCUMENTADA: CSS inline requerido por Google AdSense
|
||||||
@@ -457,6 +376,132 @@ final class AdsensePlacementRenderer
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JavaScript para posicionamiento inteligente de Rail Ads
|
||||||
|
// IMPORTANTE: Se añade DESPUES de los divs para garantizar que existan en el DOM
|
||||||
|
$html .= "
|
||||||
|
<script id=\"roi-rail-ads-positioning\">
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Configuracion
|
||||||
|
var CONFIG = {
|
||||||
|
defaultTop: {$topOffset},
|
||||||
|
gap: 30,
|
||||||
|
maxTopPercent: 0.45,
|
||||||
|
retryDelay: 100,
|
||||||
|
maxRetries: 10
|
||||||
|
};
|
||||||
|
|
||||||
|
var retryCount = 0;
|
||||||
|
|
||||||
|
function initRailAds() {
|
||||||
|
var railAds = document.querySelectorAll('.roi-rail-ad');
|
||||||
|
|
||||||
|
// Si no hay rails, reintentar (puede que el DOM no este listo)
|
||||||
|
if (!railAds.length) {
|
||||||
|
if (retryCount < CONFIG.maxRetries) {
|
||||||
|
retryCount++;
|
||||||
|
setTimeout(initRailAds, CONFIG.retryDelay);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selectores especificos del tema ROI
|
||||||
|
var navbar = document.querySelector('nav.navbar');
|
||||||
|
var hero = document.querySelector('.hero-section, .roi-hero, [class*=\"hero\"]');
|
||||||
|
var featuredImage = document.querySelector('.featured-image-container, .post-thumbnail, .entry-image');
|
||||||
|
|
||||||
|
function getNavbarHeight() {
|
||||||
|
if (navbar) {
|
||||||
|
var style = window.getComputedStyle(navbar);
|
||||||
|
if (style.position === 'fixed' || style.position === 'sticky') {
|
||||||
|
return navbar.offsetHeight || 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function adjustRailPosition() {
|
||||||
|
var navHeight = getNavbarHeight();
|
||||||
|
var newTop = CONFIG.defaultTop;
|
||||||
|
|
||||||
|
// Buscar el elemento mas bajo visible en pantalla
|
||||||
|
var elementsToCheck = [hero, featuredImage].filter(Boolean);
|
||||||
|
|
||||||
|
if (elementsToCheck.length > 0) {
|
||||||
|
var lowestBottom = 0;
|
||||||
|
|
||||||
|
elementsToCheck.forEach(function(el) {
|
||||||
|
if (el) {
|
||||||
|
var rect = el.getBoundingClientRect();
|
||||||
|
// Solo considerar si el elemento esta visible (bottom > 0)
|
||||||
|
if (rect.bottom > 0 && rect.top < window.innerHeight) {
|
||||||
|
lowestBottom = Math.max(lowestBottom, rect.bottom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Si hay un elemento visible, posicionar debajo de el
|
||||||
|
if (lowestBottom > navHeight) {
|
||||||
|
newTop = lowestBottom + CONFIG.gap;
|
||||||
|
} else {
|
||||||
|
// Si no hay hero visible, usar navbar + gap o default
|
||||||
|
newTop = Math.max(navHeight + CONFIG.gap, CONFIG.defaultTop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limitar el top maximo
|
||||||
|
var maxTop = window.innerHeight * CONFIG.maxTopPercent;
|
||||||
|
newTop = Math.min(newTop, maxTop);
|
||||||
|
|
||||||
|
// Asegurar minimo respetando navbar
|
||||||
|
newTop = Math.max(newTop, navHeight + CONFIG.gap);
|
||||||
|
|
||||||
|
// Aplicar posicion a todos los rails
|
||||||
|
for (var i = 0; i < railAds.length; i++) {
|
||||||
|
railAds[i].style.top = Math.round(newTop) + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throttle para scroll
|
||||||
|
var ticking = false;
|
||||||
|
function onScroll() {
|
||||||
|
if (!ticking) {
|
||||||
|
requestAnimationFrame(function() {
|
||||||
|
adjustRailPosition();
|
||||||
|
ticking = false;
|
||||||
|
});
|
||||||
|
ticking = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event listeners
|
||||||
|
window.addEventListener('scroll', onScroll, { passive: true });
|
||||||
|
window.addEventListener('resize', function() {
|
||||||
|
adjustRailPosition();
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
// Ejecutar inmediatamente
|
||||||
|
adjustRailPosition();
|
||||||
|
|
||||||
|
// Ejecutar varias veces para asegurar que las imagenes cargaron
|
||||||
|
setTimeout(adjustRailPosition, 200);
|
||||||
|
setTimeout(adjustRailPosition, 500);
|
||||||
|
setTimeout(adjustRailPosition, 1000);
|
||||||
|
|
||||||
|
// Tambien cuando las imagenes cargan
|
||||||
|
window.addEventListener('load', adjustRailPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iniciar
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', initRailAds);
|
||||||
|
} else {
|
||||||
|
initRailAds();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>";
|
||||||
|
|
||||||
return $html;
|
return $html;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,30 +86,37 @@ final class ThemeSettingsRenderer implements RendererInterface
|
|||||||
* Genera CSS para el layout configurable
|
* Genera CSS para el layout configurable
|
||||||
*
|
*
|
||||||
* @param array $data Datos del componente
|
* @param array $data Datos del componente
|
||||||
* @return string Bloque style o vacio si usa defaults
|
* @return string Bloque style con variables CSS y overrides
|
||||||
*/
|
*/
|
||||||
private function renderLayoutCSS(array $data): string
|
private function renderLayoutCSS(array $data): string
|
||||||
{
|
{
|
||||||
$containerWidth = $data['layout']['container_max_width'] ?? '1320';
|
$containerWidth = $data['layout']['container_max_width'] ?? '1320';
|
||||||
|
|
||||||
// Si es el valor default, no generar CSS extra
|
|
||||||
if ($containerWidth === '1320') {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validar que el valor sea seguro
|
// Validar que el valor sea seguro
|
||||||
$allowedWidths = ['1140', '1200', '1320', '1400', '100%'];
|
$allowedWidths = ['1140', '1200', '1320', '1400', '100%'];
|
||||||
if (!in_array($containerWidth, $allowedWidths, true)) {
|
if (!in_array($containerWidth, $allowedWidths, true)) {
|
||||||
return '';
|
$containerWidth = '1320'; // Fallback al default
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generar el CSS
|
// Generar el CSS
|
||||||
$widthValue = ($containerWidth === '100%') ? '100%' : $containerWidth . 'px';
|
$widthValue = ($containerWidth === '100%') ? '100%' : $containerWidth . 'px';
|
||||||
|
$widthNumeric = ($containerWidth === '100%') ? '100vw' : $containerWidth . 'px';
|
||||||
|
|
||||||
return sprintf(
|
// Siempre generar CSS variable para que otros componentes puedan usarla
|
||||||
|
$css = sprintf(
|
||||||
'<!-- Layout CSS (ROI Theme) -->
|
'<!-- Layout CSS (ROI Theme) -->
|
||||||
<style id="roi-theme-layout-css">
|
<style id="roi-theme-layout-css">
|
||||||
/* Container width override */
|
:root {
|
||||||
|
--roi-container-width: %s;
|
||||||
|
--roi-container-width-numeric: %s;
|
||||||
|
}',
|
||||||
|
esc_attr($widthValue),
|
||||||
|
esc_attr($widthNumeric)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Solo añadir override de container si NO es el default
|
||||||
|
if ($containerWidth !== '1320') {
|
||||||
|
$css .= sprintf('
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1200px) {
|
||||||
.container,
|
.container,
|
||||||
.container-lg,
|
.container-lg,
|
||||||
@@ -117,12 +124,17 @@ final class ThemeSettingsRenderer implements RendererInterface
|
|||||||
.container-xxl {
|
.container-xxl {
|
||||||
max-width: %s;
|
max-width: %s;
|
||||||
}
|
}
|
||||||
}
|
}',
|
||||||
</style>',
|
|
||||||
esc_attr($widthValue)
|
esc_attr($widthValue)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$css .= '
|
||||||
|
</style>';
|
||||||
|
|
||||||
|
return $css;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Genera contenido para wp_footer
|
* Genera contenido para wp_footer
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -226,7 +226,11 @@
|
|||||||
"default": "skyscraper",
|
"default": "skyscraper",
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"options": {
|
"options": {
|
||||||
|
"slim-small": "Slim Small (160x300)",
|
||||||
|
"slim-medium": "Slim Medium (160x400)",
|
||||||
|
"slim-large": "Slim Large (160x500)",
|
||||||
"skyscraper": "Skyscraper (160x600)",
|
"skyscraper": "Skyscraper (160x600)",
|
||||||
|
"slim-xlarge": "Slim XLarge (160x700)",
|
||||||
"wide-skyscraper": "Wide Skyscraper (160x800)",
|
"wide-skyscraper": "Wide Skyscraper (160x800)",
|
||||||
"half-page": "Half Page (300x600)",
|
"half-page": "Half Page (300x600)",
|
||||||
"large-skyscraper": "Large Skyscraper (300x1050)"
|
"large-skyscraper": "Large Skyscraper (300x1050)"
|
||||||
|
|||||||
Reference in New Issue
Block a user