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>';
|
||||
|
||||
// Format select
|
||||
// Format select - Opciones de altura para anuncios verticales
|
||||
$railFormat = $this->renderer->getFieldValue($cid, 'behavior', 'rail_format', 'skyscraper');
|
||||
$html .= $this->buildSelect($cid . 'RailFormat', 'Formato',
|
||||
$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
|
||||
$topOffset = $this->renderer->getFieldValue($cid, 'behavior', 'rail_top_offset', '150');
|
||||
$html .= $this->buildTextInput($cid . 'RailTopOffset', 'Distancia desde arriba (px)', $topOffset);
|
||||
// Top offset - Select con opciones predefinidas
|
||||
$topOffset = $this->renderer->getFieldValue($cid, 'behavior', 'rail_top_offset', '300');
|
||||
$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>';
|
||||
|
||||
@@ -246,31 +246,12 @@
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1140px;
|
||||
}
|
||||
|
||||
.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 width uses CSS variable from Theme Settings */
|
||||
.container,
|
||||
.container-lg,
|
||||
.container-xl,
|
||||
.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;
|
||||
|
||||
// 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) {
|
||||
'slim-small' => [160, 300],
|
||||
'slim-medium' => [160, 400],
|
||||
'slim-large' => [160, 500],
|
||||
'slim-xlarge' => [160, 700],
|
||||
'wide-skyscraper' => [160, 800],
|
||||
'half-page' => [300, 600],
|
||||
'large-skyscraper' => [300, 1050],
|
||||
@@ -319,14 +323,15 @@ final class AdsensePlacementRenderer
|
||||
]);
|
||||
|
||||
// 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', [
|
||||
'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
|
||||
$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
|
||||
@@ -337,94 +342,8 @@ final class AdsensePlacementRenderer
|
||||
|
||||
$css = implode("\n", $cssRules);
|
||||
|
||||
// JavaScript para posicionamiento inteligente de Rail Ads
|
||||
// Detecta dinamicamente el header/hero/featured-image y ajusta posicion
|
||||
$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";
|
||||
// HTML primero (CSS), JavaScript se añade AL FINAL
|
||||
$html = "<style>{$css}</style>\n";
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,30 +86,37 @@ final class ThemeSettingsRenderer implements RendererInterface
|
||||
* Genera CSS para el layout configurable
|
||||
*
|
||||
* @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
|
||||
{
|
||||
$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
|
||||
$allowedWidths = ['1140', '1200', '1320', '1400', '100%'];
|
||||
if (!in_array($containerWidth, $allowedWidths, true)) {
|
||||
return '';
|
||||
$containerWidth = '1320'; // Fallback al default
|
||||
}
|
||||
|
||||
// Generar el CSS
|
||||
$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) -->
|
||||
<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) {
|
||||
.container,
|
||||
.container-lg,
|
||||
@@ -117,10 +124,15 @@ final class ThemeSettingsRenderer implements RendererInterface
|
||||
.container-xxl {
|
||||
max-width: %s;
|
||||
}
|
||||
}
|
||||
</style>',
|
||||
esc_attr($widthValue)
|
||||
);
|
||||
}',
|
||||
esc_attr($widthValue)
|
||||
);
|
||||
}
|
||||
|
||||
$css .= '
|
||||
</style>';
|
||||
|
||||
return $css;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -226,7 +226,11 @@
|
||||
"default": "skyscraper",
|
||||
"editable": true,
|
||||
"options": {
|
||||
"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)"
|
||||
|
||||
Reference in New Issue
Block a user