diff --git a/Admin/ThemeSettings/Infrastructure/FieldMapping/ThemeSettingsFieldMapper.php b/Admin/ThemeSettings/Infrastructure/FieldMapping/ThemeSettingsFieldMapper.php
index 88e56c20..0d87981c 100644
--- a/Admin/ThemeSettings/Infrastructure/FieldMapping/ThemeSettingsFieldMapper.php
+++ b/Admin/ThemeSettings/Infrastructure/FieldMapping/ThemeSettingsFieldMapper.php
@@ -24,6 +24,10 @@ final class ThemeSettingsFieldMapper implements FieldMapperInterface
public function getFieldMapping(): array
{
return [
+ // Layout
+ 'themeSettingsContainerMaxWidth' => ['group' => 'layout', 'attribute' => 'container_max_width'],
+ 'themeSettingsContentColumnWidth' => ['group' => 'layout', 'attribute' => 'content_column_width'],
+
// Custom Code
'themeSettingsCustomCss' => ['group' => 'custom_code', 'attribute' => 'custom_css'],
'themeSettingsCustomJsHeader' => ['group' => 'custom_code', 'attribute' => 'custom_js_header'],
diff --git a/Admin/ThemeSettings/Infrastructure/Ui/ThemeSettingsFormBuilder.php b/Admin/ThemeSettings/Infrastructure/Ui/ThemeSettingsFormBuilder.php
index 77472a73..61724e03 100644
--- a/Admin/ThemeSettings/Infrastructure/Ui/ThemeSettingsFormBuilder.php
+++ b/Admin/ThemeSettings/Infrastructure/Ui/ThemeSettingsFormBuilder.php
@@ -27,6 +27,9 @@ final class ThemeSettingsFormBuilder
$html .= $this->buildHeader($componentId);
+ // Layout Group (nueva seccion)
+ $html .= $this->buildLayoutGroup($componentId);
+
$html .= '
';
// Columna izquierda - CSS
@@ -44,6 +47,87 @@ final class ThemeSettingsFormBuilder
return $html;
}
+ private function buildLayoutGroup(string $componentId): string
+ {
+ $html = '
';
+ $html .= '
';
+ $html .= '
';
+ $html .= ' ';
+ $html .= ' Layout y Contenedor';
+ $html .= '
';
+
+ $html .= '
';
+
+ // Container Max Width
+ $html .= '
';
+ $containerWidth = $this->renderer->getFieldValue($componentId, 'layout', 'container_max_width', '1320');
+ $containerWidthStr = is_string($containerWidth) ? $containerWidth : '1320';
+ $html .= $this->buildSelect(
+ 'themeSettingsContainerMaxWidth',
+ 'Ancho maximo del contenedor',
+ $containerWidthStr,
+ [
+ '1140' => '1140px (Bootstrap md)',
+ '1200' => '1200px (Compacto)',
+ '1320' => '1320px (Bootstrap xxl - Default)',
+ '1400' => '1400px (Amplio)',
+ '100%' => '100% (Fluido)'
+ ],
+ 'Valores menores dejan mas espacio para Rail Ads laterales'
+ );
+ $html .= '
';
+
+ // Content Column Width
+ $html .= '
';
+ $columnWidth = $this->renderer->getFieldValue($componentId, 'layout', 'content_column_width', 'col-lg-9');
+ $columnWidthStr = is_string($columnWidth) ? $columnWidth : 'col-lg-9';
+ $html .= $this->buildSelect(
+ 'themeSettingsContentColumnWidth',
+ 'Ancho columna de contenido',
+ $columnWidthStr,
+ [
+ 'col-lg-8' => '8 columnas (66.67%)',
+ 'col-lg-9' => '9 columnas (75% - Default)',
+ 'col-lg-10' => '10 columnas (83.33%)',
+ 'col-lg-12' => '12 columnas (100% sin sidebar)'
+ ],
+ 'Proporcion de la columna principal vs sidebar'
+ );
+ $html .= '
';
+
+ $html .= '
';
+
+ $html .= '
';
+ $html .= ' ';
+ $html .= ' Reduce el ancho del contenedor para dar mas espacio a los Rail Ads en pantallas grandes.';
+ $html .= '
';
+
+ $html .= '
';
+ $html .= '
';
+
+ return $html;
+ }
+
+ private function buildSelect(string $id, string $label, string $value, array $options, string $helpText = ''): string
+ {
+ $html = '
';
+ $html .= '
';
+ $html .= '
';
+ if (!empty($helpText)) {
+ $html .= '
' . esc_html($helpText) . '
';
+ }
+ $html .= '
';
+
+ return $html;
+ }
+
private function buildHeader(string $componentId): string
{
$html = '
(function() {
var railAds = document.querySelectorAll('.roi-rail-ad');
if (!railAds.length) return;
- // Buscar elementos de referencia (en orden de prioridad)
- var navbar = document.querySelector('.navbar-fixed-top, .roi-navbar, .site-header, nav.navbar');
- var hero = document.querySelector('.roi-hero, .hero-section, .hero, [class*=\"hero\"]');
- var mainContent = document.querySelector('main, .site-main, .main-content, article');
+ // 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) {
@@ -357,20 +357,33 @@ final class AdsensePlacementRenderer
function adjustRailPosition() {
var navHeight = getNavbarHeight();
- var gap = 20; // Espacio minimo de separacion
+ var gap = 30; // Espacio de separacion
var newTop = navHeight + gap;
- // Si hay hero visible, posicionar debajo de el
+ // Buscar el elemento mas bajo entre hero y featured-image
+ var lowestBottom = navHeight;
+
if (hero) {
var heroRect = hero.getBoundingClientRect();
- // Si el bottom del hero esta visible (> navbar height), rails debajo del hero
- if (heroRect.bottom > navHeight) {
- newTop = heroRect.bottom + gap;
+ if (heroRect.bottom > 0) {
+ lowestBottom = Math.max(lowestBottom, heroRect.bottom);
}
}
- // Limitar el top maximo para que no quede muy abajo
- var maxTop = window.innerHeight * 0.25; // Max 25% del viewport
+ 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
@@ -400,7 +413,8 @@ final class AdsensePlacementRenderer
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', adjustRailPosition);
} else {
- adjustRailPosition();
+ // Esperar un poco para que carguen los elementos
+ setTimeout(adjustRailPosition, 100);
}
})();
";
diff --git a/Public/ThemeSettings/Infrastructure/Ui/ThemeSettingsRenderer.php b/Public/ThemeSettings/Infrastructure/Ui/ThemeSettingsRenderer.php
index 67d3783b..c9b46ef9 100644
--- a/Public/ThemeSettings/Infrastructure/Ui/ThemeSettingsRenderer.php
+++ b/Public/ThemeSettings/Infrastructure/Ui/ThemeSettingsRenderer.php
@@ -50,6 +50,7 @@ final class ThemeSettingsRenderer implements RendererInterface
* Genera contenido para wp_head
*
* Incluye:
+ * - Layout CSS (container width)
* - Custom CSS (si configurado)
* - Custom JS Header (si configurado)
*
@@ -60,6 +61,12 @@ final class ThemeSettingsRenderer implements RendererInterface
{
$output = '';
+ // Layout CSS (container width configurable)
+ $layoutOutput = $this->renderLayoutCSS($data);
+ if (!empty($layoutOutput)) {
+ $output .= $layoutOutput . "\n";
+ }
+
// Custom CSS
$cssOutput = $this->renderCustomCSS($data);
if (!empty($cssOutput)) {
@@ -75,6 +82,47 @@ final class ThemeSettingsRenderer implements RendererInterface
return $output;
}
+ /**
+ * Genera CSS para el layout configurable
+ *
+ * @param array $data Datos del componente
+ * @return string Bloque style o vacio si usa defaults
+ */
+ 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 '';
+ }
+
+ // Generar el CSS
+ $widthValue = ($containerWidth === '100%') ? '100%' : $containerWidth . 'px';
+
+ return sprintf(
+ '
+',
+ esc_attr($widthValue)
+ );
+ }
+
/**
* Genera contenido para wp_footer
*
diff --git a/Schemas/theme-settings.json b/Schemas/theme-settings.json
index dbb1da50..b89daa28 100644
--- a/Schemas/theme-settings.json
+++ b/Schemas/theme-settings.json
@@ -1,8 +1,41 @@
{
"component_name": "theme-settings",
- "version": "1.3.0",
- "description": "Configuraciones globales del tema: codigo personalizado",
+ "version": "1.4.0",
+ "description": "Configuraciones globales del tema: layout y codigo personalizado",
"groups": {
+ "layout": {
+ "label": "Layout y Contenedor",
+ "priority": 5,
+ "fields": {
+ "container_max_width": {
+ "type": "select",
+ "label": "Ancho maximo del contenedor principal",
+ "default": "1320",
+ "editable": true,
+ "options": {
+ "1140": "1140px (Bootstrap md)",
+ "1200": "1200px (Compacto)",
+ "1320": "1320px (Bootstrap xxl - Default)",
+ "1400": "1400px (Amplio)",
+ "100%": "100% (Fluido)"
+ },
+ "description": "Ancho maximo del .container principal. Valores menores dejan mas espacio para Rail Ads"
+ },
+ "content_column_width": {
+ "type": "select",
+ "label": "Ancho columna de contenido",
+ "default": "col-lg-9",
+ "editable": true,
+ "options": {
+ "col-lg-8": "8 columnas (66.67%)",
+ "col-lg-9": "9 columnas (75% - Default)",
+ "col-lg-10": "10 columnas (83.33%)",
+ "col-lg-12": "12 columnas (100% sin sidebar)"
+ },
+ "description": "Proporcion Bootstrap de la columna principal vs sidebar"
+ }
+ }
+ },
"custom_code": {
"label": "Codigo Personalizado",
"priority": 10,