- Agrega preload de fuentes criticas (regular, 600) en wp_head priority 1
- Fuentes disponibles antes de que CSS las necesite
- Elimina flash de fuente de respaldo que causa layout shift
CLS body.wp-singular esperado: 0.171 -> ~0
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- functions-addon.php: Validacion centralizada con wp_is_mobile()
Componentes con show_on_mobile=false NO se renderizan en mobile
Previene CLS de elementos ocultos con CSS
- FeaturedImageRenderer: Agrega aspect-ratio 16/9 para reservar espacio
Imagen usa object-fit:cover con position:absolute
Metodo generateCSS() ahora publico para CriticalCSSService
- CriticalCSSService: Agrega featured-image a CRITICAL_RENDERERS
CSS se inyecta en <head> antes de que cargue contenido
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Adds minimal CSS in <head> before wp_head() to hide navbar-collapse
on mobile before Bootstrap loads. This prevents the 0.245 CLS caused
by Bootstrap calculating dimensions on an element that should be hidden.
The CSS in critical-bootstrap.css was arriving too late (via wp_head)
to prevent the initial layout shift.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
contain:layout on main-content fixed AdSense CLS but broke
navbar-collapse causing 0.575 CLS (total 0.887 vs 0.583 before)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
AdSense injects style="height: auto !important" to main-content
causing CLS 0.354. contain:layout isolates from external re-layouts.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add CSS rule to hide navbar-collapse before Bootstrap JS loads.
This prevents Bootstrap from calculating dimensions on an element
that will be hidden anyway, eliminating 0.245 CLS on mobile.
Target: reduce CLS from 0.479 to ~0.234
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Remove data-bs-spy, data-bs-target, and data-bs-offset attributes
from body tag in header.php. The target element .toc-container does
not exist, causing Bootstrap ScrollSpy to fail and trigger layout
recalculations that account for 82% of the CLS score (0.946 of 1.147).
Ref: 99.02-investigacion-cls-mobile.md
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
El min-height:50vh en .site-main causaba un CLS de ~1.5 porque:
- Reservaba 50% del viewport inicialmente
- Cuando el contenido real cargaba, generaba un shift enorme
.site-main ya tiene flex-grow:1 que es suficiente para el layout.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
El path a critical-bootstrap.css usaba 'css' (minúscula) pero
el directorio real es 'Css' (mayúscula). Esto causaba que el
CSS crítico no se cargara en producción (Linux es case-sensitive).
Bug encontrado: El CriticalBootstrapService no inyectaba el CSS
en producción porque file_exists() retornaba false.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Bootstrap .btn class used above-the-fold in navbar needs to be
in critical CSS to prevent CLS when Bootstrap is deferred.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove roi-main-style from deferred list
- Restore media='all' for style.css
- CLS was 0.392 when deferred, should return to ~0.06
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add @font-face declarations to critical-bootstrap.css inline
- Add critical CSS variables (colors, fonts) inline
- Defer fonts.css (utilities only, @font-face inline)
- Defer variables.css (critical vars inline)
Now only style.css is render-blocking (~19KB)
All other CSS deferred with media=print + onload
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add grid system (row, col-*) to critical-bootstrap.css to prevent CLS
- Add text utilities, sizing, spacing, and alert component to critical CSS
- Enable CriticalBootstrapService to inline critical Bootstrap in <head>
- Defer bootstrap-subset.min.css (21KB) via media=print + onload
- Fix preload pointing to wrong Bootstrap file (was 227KB, now 147KB)
Expected improvement: ~970ms reduction in render-blocking CSS
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The toast from IP View Limit plugin uses Bootstrap classes that weren't
being detected because PurgeCSS only scans theme files, not plugins.
Added to safelist: toast-container, toast, toast-body, position-fixed,
bottom-0, end-0, text-dark, bg-warning, btn-close, m-auto
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
El toast de "consultas restantes" no se mostraba porque las clases
.toast* fueron eliminadas por PurgeCSS. Agregado /toast/ al safelist.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Genera bootstrap-subset.min.css con solo clases usadas (36% reduccion)
- Actualiza enqueue-scripts.php para usar el subset
- Agrega scripts de build: npm run build:bootstrap
- Mejora LCP al reducir tiempo de parseo CSS
Impacto estimado:
- Bootstrap: 227KB -> 145KB (-82KB)
- Tiempo parseo CSS: ~800ms -> ~500ms
- LCP: mejora esperada ~300-500ms
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Cuando el ScrollSpy detecta un nuevo heading activo,
el TOC sidebar ahora hace scroll automático para
mostrar el elemento activo usando scrollIntoView
con behavior: smooth y block: nearest.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Agrega outline: none para .toc-link:focus via CSSGenerator
para remover el borde azul del browser al hacer clic.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problema: Los headings no tenían atributo id porque el filtro
PHP the_content se agregaba después de procesar el contenido.
Solución: El script del TOC ahora:
1. Busca cada link del TOC
2. Encuentra el heading correspondiente por texto
3. Asigna el ID esperado al heading
4. Luego configura smooth scroll e IntersectionObserver
Esto resuelve:
- Links del TOC no clickeables
- Smooth scroll no funcionaba
- ScrollSpy no rastreaba secciones
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
El método getVisibilityClasses() existía pero no se usaba.
Ahora buildClasses() verifica show_on_desktop y show_on_mobile
para aplicar clases Bootstrap (d-none d-lg-block, d-lg-none, etc.)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Replace scroll event listener with Intersection Observer in TableOfContentsRenderer
- Eliminates ~100ms forced reflows from offsetTop reads during scroll
- Revert Bootstrap CSS to blocking (media='all') - deferring caused CLS 0.954
- Keep CriticalBootstrapService available for future optimization
- Simplify CriticalCSSHooksRegistrar to only use CriticalCSSService
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Created CriticalCSSService (singleton) that queries BD directly in wp_head
- Service generates CSS BEFORE components render (priority 1)
- Renderers check is_critical flag and skip inline CSS if true
- Made generateCSS() public in Renderers for CriticalCSSService to use
- Removed CriticalCSSCollector pattern (timing issue with WordPress)
Flow:
1. wp_head (priority 1) → CriticalCSSService::render()
2. Service queries BD for components with visibility.is_critical=true
3. Generates CSS using Renderer->generateCSS() methods
4. Outputs: <style id="roi-critical-css">...</style>
5. When Renderers execute, they detect is_critical and omit CSS inline
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
PROBLEMA:
- wp_head() se ejecuta ANTES de que los componentes rendericen
- CriticalCSSCollector estaba vacío al momento de inyectar en <head>
- Componentes retornaban solo HTML sin CSS → sitio sin estilos
SOLUCIÓN:
- Eliminar lógica condicional de is_critical en render()
- Siempre incluir CSS inline con el componente (comportamiento anterior)
- Campo is_critical reservado para futura implementación con output buffering
Archivos modificados:
- NavbarRenderer.php
- TopNotificationBarRenderer.php
- HeroRenderer.php
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Implementar sistema de grupos de componentes tipo "carpetas de apps"
- Crear ComponentGroupRegistry para gestionar grupos y componentes
- Añadir vista home con grupos: Header, Contenido, CTAs, Engagement, Forms, Config
- Rediseñar UI con Design System: header navy, cards blancos, mini-cards verticales
- Incluir animaciones fadeInUp escalonadas y efectos hover con glow
- Mantener navegación a vistas de componentes individuales
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Optimización PageSpeed Fase 4.1:
- Cambio font-display:block → font-display:swap en bootstrap-icons.min.css
- Reduce bloqueo de renderizado en ~420ms
- Permite mostrar fallback mientras carga la fuente de iconos
Archivos subset ya tenían swap configurado (sin cambios necesarios)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
El ins.adsbygoogle tenia width 0 porque el contenedor padre
no tenia ancho definido. Esto causaba el error
"No slot size for availableWidth=0" de AdSense.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- 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>
Anchor Ads:
- Ocultos por defecto via CSS (opacity: 0, visibility: hidden)
- Solo se muestran cuando AdSense llena el slot (clase .ad-loaded)
- Ya no aparece espacio en blanco si no hay anuncio
Vignette Ads:
- Agregados tamaños: 728x90, 970x250, 970x90, 468x60, 320x100
- Nueva opción "auto" (recomendado) para formato automático
- Renderer actualizado para manejar todos los tamaños
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fix TypeError: buildSelect() Argument #3 ($value) must be of type string.
The getFieldValue() method can return boolean for certain DB values,
causing type mismatch with strict typed buildSelect() method.
Added (string) casts to all buildSelect() calls in Vignette Ads section:
- vignetteTrigger
- size
- opacity
- reshowTime
- maxSession
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Agregar watchUnfilledAds() con MutationObserver
- Detectar data-ad-status="unfilled" de AdSense
- Timeout de 5s como respaldo si no hay contenido
- Evitar mostrar espacio en blanco cuando no hay anuncio
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Anchor Ads: anuncios fijos top/bottom con botones minimizar/cerrar
- Vignette Ads: modal fullscreen con triggers configurables
- Schema v1.3.0 con grupos anchor_ads y vignette_ads (18 campos)
- FieldMapper actualizado para persistir settings en BD
- JavaScript para interacción (colapso, cierre, localStorage)
- Soporte para responsive y tamaños fijos en vignette
IMPORTANTE: Ejecutar en servidor remoto:
wp roi-theme sync-component adsense-placement
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Nuevo campo hide_for_logged_in en schema visibility group
- Switch en panel de administracion con icono bi-person-lock
- Mapeo en FieldMapper para persistencia
- Validacion en roi_render_ad_slot, roi_render_rail_ads,
roi_enqueue_adsense_script y roi_inject_content_ads
- No carga script ni muestra ads si usuario tiene sesion activa
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- El slot after-related estaba definido en schema pero no se llamaba
- Ahora se renderiza después del componente related-post
- Requiere habilitar 'Después de Related Posts' en panel AdSense
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>