diff --git a/Assets/CriticalCSS/responsive.critical.css b/Assets/CriticalCSS/responsive.critical.css new file mode 100644 index 00000000..53898bd8 --- /dev/null +++ b/Assets/CriticalCSS/responsive.critical.css @@ -0,0 +1 @@ +@media (max-width:575.98px){:root{--bs-gutter-x:1rem}body{font-size:14px}h1{font-size:24px}h2{font-size:20px}h3{font-size:18px}.container-fluid{padding:0 10px}.navbar{padding:0.5rem 0}.navbar-brand{font-size:18px}main{padding:0.5rem}.sidebar{margin-top:2rem}table{font-size:12px;margin-bottom:1rem;overflow-x:auto}.table-responsive{margin-bottom:1rem}.btn{padding:0.375rem 0.75rem;font-size:14px}.btn-lg{padding:0.5rem 1rem;font-size:16px}.card{margin-bottom:1rem}.form-group{margin-bottom:1rem}.form-control{padding:0.375rem 0.75rem;font-size:16px}.modal-dialog{margin:0.5rem}.modal-content{border-radius:4px}img{max-width:100%;height:auto}ul,ol{padding-left:1.5rem}.mt-1,.my-1{margin-top:0.25rem !important}.mb-1,.my-1{margin-bottom:0.25rem !important}.p-1{padding:0.25rem !important}}@media (min-width:576px){body{font-size:14px}h1{font-size:28px}h2{font-size:22px}h3{font-size:18px}}@media (min-width:768px){body{font-size:15px}h1{font-size:32px}h2{font-size:26px}h3{font-size:20px}.row-md-2{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem}.navbar{padding:1rem 0}.main-content{display:grid;grid-template-columns:1fr 300px;gap:2rem}.main-content.no-sidebar{grid-template-columns:1fr}}@media (min-width:992px){body{font-size:16px}h1{font-size:36px}h2{font-size:28px}h3{font-size:22px}.row-lg-3{display:grid;grid-template-columns:repeat(3,1fr);gap:2rem}.main-content{display:grid;grid-template-columns:1fr 300px;gap:2rem}.main-content.with-left-sidebar{grid-template-columns:250px 1fr 300px}.content-wrapper{max-width:1200px;margin:0 auto}} \ No newline at end of file diff --git a/Assets/CriticalCSS/variables.critical.css b/Assets/CriticalCSS/variables.critical.css new file mode 100644 index 00000000..593b9c27 --- /dev/null +++ b/Assets/CriticalCSS/variables.critical.css @@ -0,0 +1 @@ +:root{--color-navy-dark:#0E2337;--color-navy-primary:#1e3a5f;--color-navy-light:#2c5282;--color-blue-primary:#1e3a5f;--color-blue-secondary:#2c5282;--color-blue-light:#1a73e8;--color-cyan-primary:#61c7cd;--color-cyan-dark:#4db8c4;--color-cyan-darker:#4fb3b9;--color-orange-primary:#FF8600;--color-orange-secondary:#FFB800;--color-orange-light:#FFB800;--color-orange-button:#FF6B35;--color-orange-button-end:#FF8C42;--color-orange-hover:#FF6B35;--color-neutral-50:#f8f9fa;--color-neutral-100:#e9ecef;--color-neutral-600:#495057;--color-neutral-700:#6c757d;--color-slate-gray:#4C5C6B;--color-gray-50:#f8f9fa;--color-gray-100:#f7fafc;--color-gray-200:#e9ecef;--color-gray-300:#dee2e6;--color-gray-400:#cbd5e0;--color-gray-500:#a0aec0;--color-gray-600:#6c757d;--color-gray-700:#495057;--color-gray-800:#333;--color-gray-900:#212529;--color-gray-dark:#1a1a1a;--color-white:#ffffff;--color-black:#000000;--font-family-base:'Poppins',sans-serif;--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--font-size-base:1rem;--font-size-sm:0.875rem;--font-size-lg:1.125rem;--font-size-xl:1.25rem;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--line-height-base:1.5;--line-height-tight:1.25;--line-height-loose:1.8;--spacing-xs:0.25rem;--spacing-sm:0.5rem;--spacing-md:1rem;--spacing-lg:1.5rem;--spacing-xl:2rem;--spacing-2xl:3rem;--spacing-3xl:4rem;--border-width:1px;--border-width-thick:2px;--border-width-thicker:3px;--border-width-lateral:4px;--border-radius-sm:4px;--border-radius-md:8px;--border-radius-lg:12px;--border-radius-xl:16px;--border-color-light:var(--color-gray-200);--border-color-default:var(--color-gray-300);--shadow-xs:0 1px 2px rgba(0,0,0,0.05);--shadow-sm:0 2px 4px rgba(0,0,0,0.1);--shadow-md:0 4px 12px rgba(0,0,0,0.15);--shadow-lg:0 8px 24px rgba(0,0,0,0.2);--shadow-xl:0 12px 32px rgba(0,0,0,0.25);--shadow-2xl:0 20px 60px rgba(0,0,0,0.3);--shadow-navbar:0 2px 4px rgba(0,0,0,0.15);--shadow-navbar-scrolled:0 4px 12px rgba(0,0,0,0.25);--shadow-dropdown:0 8px 24px rgba(0,0,0,0.12);--shadow-cta:0 8px 24px rgba(255,133,0,0.3);--shadow-cta-hover:0 12px 32px rgba(255,133,0,0.4);--shadow-button:0 4px 12px rgba(255,107,53,0.3);--shadow-related-posts:0 12px 32px rgba(26,115,232,0.15);--shadow-pagination:0 4px 12px rgba(26,115,232,0.3);--transition-fast:0.15s ease;--transition-base:0.3s ease;--transition-slow:0.5s ease;--transition-cubic:cubic-bezier(0.4,0,0.2,1);--z-dropdown:1000;--z-sticky:1020;--z-navbar:1030;--z-modal-backdrop:1040;--z-modal:1050;--z-popover:1060;--z-tooltip:1070;--gradient-hero:linear-gradient(135deg,var(--color-blue-primary) 0%,var(--color-blue-secondary) 100%);--gradient-cta:linear-gradient(135deg,var(--color-orange-primary) 0%,var(--color-orange-secondary) 100%);--gradient-button-lets-talk:linear-gradient(135deg,var(--color-orange-button) 0%,var(--color-orange-button-end) 100%);--gradient-pagination:linear-gradient(135deg,var(--color-blue-primary) 0%,var(--color-blue-secondary) 100%);--gradient-underline:linear-gradient(90deg,var(--color-cyan-primary) 0%,var(--color-cyan-dark) 100%);--gradient-border-related:linear-gradient(180deg,var(--color-blue-primary) 0%,var(--color-blue-light) 100%);--opacity-disabled:0.5;--opacity-hover:0.8;--opacity-backdrop:0.5;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--breakpoint-xxl:1400px} \ No newline at end of file diff --git a/Assets/Js/lazy-css-loader.js b/Assets/Js/lazy-css-loader.js new file mode 100644 index 00000000..a73c938a --- /dev/null +++ b/Assets/Js/lazy-css-loader.js @@ -0,0 +1,135 @@ +/** + * TIPO 5: Lazy CSS Loader + * + * Carga CSS no critico despues del evento load usando: + * - requestIdleCallback para CSS de baja prioridad + * - Event listeners para CSS condicional + * + * @package ROITheme + * @since 1.0.20 + */ +(function() { + 'use strict'; + + // Configuracion de CSS lazy (inyectada desde PHP) + var config = window.roiLazyCSSConfig || { + baseUrl: '', + version: '1.0.0', + idleTimeout: 2000, + cssFiles: [] + }; + + /** + * Carga un archivo CSS de forma asincrona + * + * @param {string} href URL del archivo CSS + * @param {string} id ID del elemento link + * @returns {Promise} + */ + function loadCSS(href, id) { + // Evitar duplicados + if (document.getElementById(id)) { + return Promise.resolve(); + } + + return new Promise(function(resolve, reject) { + var link = document.createElement('link'); + link.id = id; + link.rel = 'stylesheet'; + link.href = href; + link.onload = resolve; + link.onerror = reject; + document.head.appendChild(link); + }); + } + + /** + * Carga CSS cuando el navegador esta idle + * + * @param {Array} files Lista de archivos a cargar + */ + function loadOnIdle(files) { + var load = function() { + files.forEach(function(file) { + loadCSS( + config.baseUrl + file.path + '?ver=' + config.version, + 'roi-lazy-' + file.id + ); + }); + }; + + if ('requestIdleCallback' in window) { + requestIdleCallback(load, { timeout: config.idleTimeout }); + } else { + // Fallback para Safari + setTimeout(load, config.idleTimeout); + } + } + + /** + * Carga CSS de print solo cuando se va a imprimir + * + * @param {Object} file Archivo de print CSS + */ + function setupPrintCSS(file) { + var loaded = false; + + var load = function() { + if (loaded) return; + loaded = true; + loadCSS( + config.baseUrl + file.path + '?ver=' + config.version, + 'roi-lazy-print' + ); + }; + + // Evento antes de imprimir + window.addEventListener('beforeprint', load); + + // Fallback: detectar Ctrl+P / Cmd+P + document.addEventListener('keydown', function(e) { + if ((e.ctrlKey || e.metaKey) && e.key === 'p') { + load(); + } + }); + } + + /** + * Inicializacion + */ + function init() { + var idleFiles = []; + var printFile = null; + + config.cssFiles.forEach(function(file) { + switch (file.trigger) { + case 'idle': + idleFiles.push(file); + break; + case 'print': + printFile = file; + break; + } + }); + + // Cargar CSS idle despues de que la pagina este lista + if (idleFiles.length > 0) { + if (document.readyState === 'complete') { + loadOnIdle(idleFiles); + } else { + window.addEventListener('load', function() { + loadOnIdle(idleFiles); + }); + } + } + + // Configurar CSS de print + if (printFile) { + setupPrintCSS(printFile); + } + } + + // Iniciar + init(); + +})(); diff --git a/Inc/enqueue-scripts.php b/Inc/enqueue-scripts.php index c500efc7..7469da8c 100644 --- a/Inc/enqueue-scripts.php +++ b/Inc/enqueue-scripts.php @@ -512,14 +512,16 @@ function roi_enqueue_theme_styles() { // ); // Theme Animations - // DIFERIDO: Fase 4.2 PageSpeed - mejoras visuales no críticas - wp_enqueue_style( - 'roi-animations', - get_template_directory_uri() . '/Assets/Css/css-global-animations.css', - array('roi-bootstrap'), - '1.0.0', - 'print' // Diferido para no bloquear renderizado - ); + // MIGRADO A TIPO 5: Lazy loading via LazyCSSRegistrar (requestIdleCallback) + // Fecha: 2025-12-01 + // @see Public/LazyCSSLoader/Infrastructure/Services/LazyCSSRegistrar.php + // wp_enqueue_style( + // 'roi-animations', + // get_template_directory_uri() . '/Assets/Css/css-global-animations.css', + // array('roi-bootstrap'), + // '1.0.0', + // 'print' + // ); // Theme Responsive Styles // DIFERIDO: Fase 4.3 - media queries no críticas para primer paint @@ -542,13 +544,16 @@ function roi_enqueue_theme_styles() { ); // Print Styles - wp_enqueue_style( - 'roi-print', - get_template_directory_uri() . '/Assets/Css/css-global-print.css', - array(), - '1.0.0', - 'print' - ); + // MIGRADO A TIPO 5: Lazy loading via LazyCSSRegistrar (beforeprint event) + // Fecha: 2025-12-01 + // @see Public/LazyCSSLoader/Infrastructure/Services/LazyCSSRegistrar.php + // wp_enqueue_style( + // 'roi-print', + // get_template_directory_uri() . '/Assets/Css/css-global-print.css', + // array(), + // '1.0.0', + // 'print' + // ); } add_action('wp_enqueue_scripts', 'roi_enqueue_theme_styles', 13); diff --git a/Public/CriticalCSS/Application/UseCases/GetCriticalCSSUseCase.php b/Public/CriticalCSS/Application/UseCases/GetCriticalCSSUseCase.php new file mode 100644 index 00000000..d2b1f8e2 --- /dev/null +++ b/Public/CriticalCSS/Application/UseCases/GetCriticalCSSUseCase.php @@ -0,0 +1,49 @@ +cache->has($type)) { + $this->extractor->generateAll(); + } + + return $this->cache->get($type) ?? ''; + } + + /** + * Fuerza regeneracion de todo el CSS critico + * + * @return array Mapa de tipo => bytes generados + */ + public function regenerate(): array + { + $this->cache->clear(); + return $this->extractor->generateAll(); + } +} diff --git a/Public/CriticalCSS/Domain/Contracts/CriticalCSSCacheInterface.php b/Public/CriticalCSS/Domain/Contracts/CriticalCSSCacheInterface.php new file mode 100644 index 00000000..f615583c --- /dev/null +++ b/Public/CriticalCSS/Domain/Contracts/CriticalCSSCacheInterface.php @@ -0,0 +1,59 @@ + Mapa de tipo => bytes generados + */ + public function generateAll(): array; + + /** + * Verifica si algun archivo fuente ha cambiado + * respecto al cache generado + * + * Usado para auto-regeneracion en el bootstrap. + * + * @return bool True si se necesita regenerar + */ + public function needsRegeneration(): bool; +} diff --git a/Public/CriticalCSS/Infrastructure/Cache/CriticalCSSFileCache.php b/Public/CriticalCSS/Infrastructure/Cache/CriticalCSSFileCache.php new file mode 100644 index 00000000..412c4be8 --- /dev/null +++ b/Public/CriticalCSS/Infrastructure/Cache/CriticalCSSFileCache.php @@ -0,0 +1,117 @@ +cacheDir = get_template_directory() . '/Assets/CriticalCSS'; + } + + /** + * {@inheritDoc} + */ + public function get(string $key): ?string + { + $file = $this->getFilePath($key); + + if (!file_exists($file)) { + return null; + } + + $content = file_get_contents($file); + + return $content !== false ? $content : null; + } + + /** + * {@inheritDoc} + */ + public function set(string $key, string $css): bool + { + $this->ensureDirectoryExists(); + $file = $this->getFilePath($key); + + return file_put_contents($file, $css) !== false; + } + + /** + * {@inheritDoc} + */ + public function has(string $key): bool + { + return file_exists($this->getFilePath($key)); + } + + /** + * {@inheritDoc} + */ + public function clear(): void + { + $files = glob($this->cacheDir . '/*.critical.css'); + + if ($files === false) { + return; + } + + foreach ($files as $file) { + unlink($file); + } + } + + /** + * {@inheritDoc} + * + * Compara timestamps para detectar cache desactualizado. + * Costo: ~0.1ms (2 llamadas a filemtime) + */ + public function isStale(string $key, string $sourceFile): bool + { + $cacheFile = $this->getFilePath($key); + + // Si no existe cache, esta desactualizado + if (!file_exists($cacheFile)) { + return true; + } + + // Si no existe fuente, no esta desactualizado (nada que regenerar) + if (!file_exists($sourceFile)) { + return false; + } + + // Comparar timestamps + return filemtime($sourceFile) > filemtime($cacheFile); + } + + /** + * Genera ruta del archivo cache + */ + private function getFilePath(string $key): string + { + return $this->cacheDir . '/' . $key . '.critical.css'; + } + + /** + * Asegura que el directorio de cache existe + */ + private function ensureDirectoryExists(): void + { + if (!is_dir($this->cacheDir)) { + mkdir($this->cacheDir, 0755, true); + } + } +} diff --git a/Public/CriticalCSS/Infrastructure/Services/CriticalCSSExtractor.php b/Public/CriticalCSS/Infrastructure/Services/CriticalCSSExtractor.php new file mode 100644 index 00000000..40850a79 --- /dev/null +++ b/Public/CriticalCSS/Infrastructure/Services/CriticalCSSExtractor.php @@ -0,0 +1,147 @@ +minify($css); + } + + /** + * {@inheritDoc} + */ + public function extractResponsive(): string + { + $sourceFile = get_template_directory() . self::SOURCE_RESPONSIVE; + + if (!file_exists($sourceFile)) { + return ''; + } + + $css = file_get_contents($sourceFile); + + if ($css === false) { + return ''; + } + + $criticalCSS = ''; + + // Extraer solo media queries criticas + // Regex: captura @media (...) { contenido con 1 nivel de {} } + foreach (self::CRITICAL_BREAKPOINTS as $breakpoint) { + $pattern = '/' . preg_quote($breakpoint, '/') . '\s*\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/s'; + + if (preg_match_all($pattern, $css, $matches)) { + foreach ($matches[0] as $match) { + $criticalCSS .= $match . "\n"; + } + } + } + + return $this->minify($criticalCSS); + } + + /** + * {@inheritDoc} + */ + public function generateAll(): array + { + $results = []; + + // Variables CSS + $variablesCSS = $this->extractVariables(); + if (!empty($variablesCSS)) { + $this->cache->set('variables', $variablesCSS); + $results['variables'] = strlen($variablesCSS); + } + + // Responsive critico + $responsiveCSS = $this->extractResponsive(); + if (!empty($responsiveCSS)) { + $this->cache->set('responsive', $responsiveCSS); + $results['responsive'] = strlen($responsiveCSS); + } + + return $results; + } + + /** + * {@inheritDoc} + */ + public function needsRegeneration(): bool + { + $templateDir = get_template_directory(); + + return $this->cache->isStale('variables', $templateDir . self::SOURCE_VARIABLES) + || $this->cache->isStale('responsive', $templateDir . self::SOURCE_RESPONSIVE); + } + + /** + * Minifica CSS eliminando espacios y comentarios innecesarios + */ + private function minify(string $css): string + { + // Eliminar comentarios + $css = preg_replace('/\/\*[\s\S]*?\*\//', '', $css) ?? $css; + + // Eliminar espacios innecesarios + $css = preg_replace('/\s+/', ' ', $css) ?? $css; + $css = preg_replace('/\s*([{};:,>+~])\s*/', '$1', $css) ?? $css; + + // Eliminar ultimo punto y coma antes de } + $css = preg_replace('/;}/', '}', $css) ?? $css; + + return trim($css); + } +} diff --git a/Public/CriticalCSS/Infrastructure/Services/CriticalCSSInjector.php b/Public/CriticalCSS/Infrastructure/Services/CriticalCSSInjector.php new file mode 100644 index 00000000..72c19cca --- /dev/null +++ b/Public/CriticalCSS/Infrastructure/Services/CriticalCSSInjector.php @@ -0,0 +1,86 @@ +cache->get('variables'); + + if (empty($css)) { + return; + } + + printf( + '' . "\n" . + '' . "\n", + $css + ); + } + + /** + * Inyecta media queries criticas + */ + public function injectResponsive(): void + { + $css = $this->cache->get('responsive'); + + if (empty($css)) { + return; + } + + printf( + '' . "\n" . + '' . "\n", + $css + ); + } + + /** + * Deshabilita enqueue de archivos que ahora estan inline + */ + public function dequeueInlinedCSS(): void + { + // Variables ya inline - no cargar archivo externo + if ($this->cache->has('variables')) { + wp_dequeue_style('roi-variables'); + wp_deregister_style('roi-variables'); + } + } +} diff --git a/Public/CustomCSSManager/Infrastructure/Services/CustomCSSInjector.php b/Public/CustomCSSManager/Infrastructure/Services/CustomCSSInjector.php index 288935df..cf6da9e6 100644 --- a/Public/CustomCSSManager/Infrastructure/Services/CustomCSSInjector.php +++ b/Public/CustomCSSManager/Infrastructure/Services/CustomCSSInjector.php @@ -23,8 +23,9 @@ final class CustomCSSInjector */ public function register(): void { - // CSS crítico: priority 2 (después de componentes, antes de theme-settings) - add_action('wp_head', [$this, 'injectCriticalCSS'], 2); + // CSS crítico: priority 3 (después de TIPO 4 responsive P:2, antes de theme-settings P:5) + // NOTA: Cambiado de P:2 a P:3 para evitar colision con roi-critical-responsive + add_action('wp_head', [$this, 'injectCriticalCSS'], 3); // CSS diferido: priority alta en footer add_action('wp_footer', [$this, 'injectDeferredCSS'], 10); diff --git a/Public/LazyCSSLoader/Infrastructure/Contracts/LazyCSSRegistrarInterface.php b/Public/LazyCSSLoader/Infrastructure/Contracts/LazyCSSRegistrarInterface.php new file mode 100644 index 00000000..203c148b --- /dev/null +++ b/Public/LazyCSSLoader/Infrastructure/Contracts/LazyCSSRegistrarInterface.php @@ -0,0 +1,34 @@ + + */ + private const LAZY_CSS_FILES = [ + [ + 'id' => 'animations', + 'path' => '/Assets/Css/css-global-animations.css', + 'trigger' => 'idle', + 'handle' => 'roi-animations', + ], + [ + 'id' => 'print', + 'path' => '/Assets/Css/css-global-print.css', + 'trigger' => 'print', + 'handle' => 'roi-print', + ], + ]; + + /** + * Timeout en ms para requestIdleCallback + */ + private const IDLE_TIMEOUT = 2000; + + /** + * Registra hooks de WordPress + */ + public function register(): void + { + // Dequeue CSS que ahora es TIPO 5 + add_action('wp_enqueue_scripts', [$this, 'dequeueType5CSS'], 999); + + // Agregar script loader y configuracion + add_action('wp_enqueue_scripts', [$this, 'enqueueLoader'], 20); + + // Agregar fallback noscript + add_action('wp_head', [$this, 'addNoscriptFallback'], 99); + } + + /** + * Remueve CSS que ahora es TIPO 5 del enqueue normal + */ + public function dequeueType5CSS(): void + { + foreach (self::LAZY_CSS_FILES as $file) { + wp_dequeue_style($file['handle']); + wp_deregister_style($file['handle']); + } + } + + /** + * Encola el loader JavaScript con configuracion + */ + public function enqueueLoader(): void + { + $loaderPath = get_template_directory() . '/Assets/Js/lazy-css-loader.js'; + + // Solo encolar si el archivo existe + if (!file_exists($loaderPath)) { + if (defined('WP_DEBUG') && WP_DEBUG) { + error_log('ROI Theme TIPO 5: lazy-css-loader.js not found'); + } + return; + } + + wp_enqueue_script( + 'roi-lazy-css-loader', + get_template_directory_uri() . '/Assets/Js/lazy-css-loader.js', + [], + ROI_VERSION, + true // En footer + ); + + // Pasar configuracion al JS + wp_localize_script('roi-lazy-css-loader', 'roiLazyCSSConfig', [ + 'baseUrl' => get_template_directory_uri(), + 'version' => ROI_VERSION, + 'idleTimeout' => self::IDLE_TIMEOUT, + 'cssFiles' => self::LAZY_CSS_FILES, + ]); + } + + /** + * Agrega fallback noscript para accesibilidad + */ + public function addNoscriptFallback(): void + { + echo '' . "\n"; + } +} diff --git a/bin/generate-critical-css.php b/bin/generate-critical-css.php new file mode 100644 index 00000000..a27e372b --- /dev/null +++ b/bin/generate-critical-css.php @@ -0,0 +1,73 @@ +clear(); + WP_CLI::log('Cache limpiado.'); + } + + WP_CLI::log('Generando CSS critico TIPO 4...'); + + $results = $useCase->regenerate(); + + if (empty($results)) { + WP_CLI::warning('No se genero CSS critico. Verifica que existan los archivos fuente.'); + return; + } + + foreach ($results as $type => $size) { + WP_CLI::success(sprintf( + '%s.critical.css: %s bytes', + $type, + number_format($size) + )); + } + + WP_CLI::success('CSS critico TIPO 4 generado exitosamente.'); + } +} + +WP_CLI::add_command('roi-theme generate-critical-css', 'ROI_CriticalCSS_Command'); diff --git a/functions.php b/functions.php index 64f2a59f..ab25190e 100644 --- a/functions.php +++ b/functions.php @@ -338,6 +338,64 @@ if (defined('WP_DEBUG') && WP_DEBUG) { error_log('ROI Theme: Bootstrap completed successfully'); } +// ============================================================================= +// 5.4. CRITICAL CSS TIPO 4 (CSS Below-the-fold) +// ============================================================================= + +/** + * Bootstrap CriticalCSS TIPO 4 + * + * Inyecta CSS critico (variables, responsive) inline en wp_head + * antes de cualquier otro CSS para evitar FOUC. + * + * Incluye auto-regeneracion: si los archivos fuente cambian, + * el cache se regenera automaticamente sin intervencion manual. + * Costo: ~0.1ms (2 llamadas a filemtime()) + */ +if (!is_admin()) { + $criticalCSSCache = new \ROITheme\Public\CriticalCSS\Infrastructure\Cache\CriticalCSSFileCache(); + $criticalCSSExtractor = new \ROITheme\Public\CriticalCSS\Infrastructure\Services\CriticalCSSExtractor( + $criticalCSSCache + ); + + // Auto-regenerar si archivos fuente cambiaron + if ($criticalCSSExtractor->needsRegeneration()) { + $criticalCSSExtractor->generateAll(); + } + + $criticalCSSInjector = new \ROITheme\Public\CriticalCSS\Infrastructure\Services\CriticalCSSInjector( + $criticalCSSCache + ); + $criticalCSSInjector->register(); +} + +// Registrar comando WP-CLI (util para forzar regeneracion o limpiar cache) +if (defined('WP_CLI') && WP_CLI) { + require_once get_template_directory() . '/bin/generate-critical-css.php'; +} + +// ============================================================================= +// 5.5. LAZY CSS LOADER TIPO 5 (CSS No Critico) +// ============================================================================= + +/** + * Bootstrap LazyCSSLoader TIPO 5 + * + * Carga CSS no critico de forma lazy: + * - Animaciones: requestIdleCallback o timeout 2s + * - Print: solo cuando usuario va a imprimir (beforeprint event) + * + * @since 1.0.20 + */ +if (!is_admin()) { + $lazyCSSRegistrar = new \ROITheme\Public\LazyCSSLoader\Infrastructure\Services\LazyCSSRegistrar(); + $lazyCSSRegistrar->register(); + + if (defined('WP_DEBUG') && WP_DEBUG) { + error_log('ROI Theme: TIPO 5 LazyCSSLoader registered'); + } +} + // ============================================================================= // 6. INSTALACIÓN DE TABLAS DEL TEMA // =============================================================================