- Fix hero badge min-height from 32px to 36px (actual height is 35px) - Add vertical-align: middle to badge for better alignment - Add critical CSS for .analisis and .desglose tables: - table-layout: fixed to prevent reflow - overflow-x: auto for mobile horizontal scroll - These changes target CLS 0.117 caused by hero badges 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
438 lines
12 KiB
PHP
438 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* Critical CSS Generator and Inline Loader
|
|
*
|
|
* This file provides functionality to inline critical CSS for above-the-fold content,
|
|
* improving First Contentful Paint (FCP) and Largest Contentful Paint (LCP).
|
|
*
|
|
* IMPORTANT: This feature is DISABLED by default. Enable it via Customizer.
|
|
*
|
|
* How it works:
|
|
* 1. When enabled, critical CSS is inlined in the <head>
|
|
* 2. Main stylesheet is loaded asynchronously after page load
|
|
* 3. Improves Core Web Vitals by reducing render-blocking CSS
|
|
*
|
|
* @package ROI_Theme
|
|
* @since 1.0.0
|
|
*/
|
|
|
|
// Exit if accessed directly.
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Check if Critical CSS is enabled
|
|
*
|
|
* @since 1.0.0
|
|
* @return bool
|
|
*/
|
|
function roi_is_critical_css_enabled() {
|
|
return get_theme_mod( 'roi_enable_critical_css', false );
|
|
}
|
|
|
|
/**
|
|
* Get Critical CSS Content
|
|
*
|
|
* Returns the critical CSS for the current page type.
|
|
* You can customize this based on page types (home, single, archive, etc.)
|
|
*
|
|
* @since 1.0.0
|
|
* @return string Critical CSS content
|
|
*/
|
|
function roi_get_critical_css() {
|
|
// Define critical CSS based on page type
|
|
$critical_css = '';
|
|
|
|
// Get transient to cache critical CSS
|
|
$transient_key = 'roi_critical_css_' . roi_get_page_type();
|
|
$cached_css = get_transient( $transient_key );
|
|
|
|
if ( false !== $cached_css ) {
|
|
return $cached_css;
|
|
}
|
|
|
|
// Generate critical CSS based on page type
|
|
if ( is_front_page() || is_home() ) {
|
|
$critical_css = roi_get_home_critical_css();
|
|
} elseif ( is_single() ) {
|
|
$critical_css = roi_get_single_critical_css();
|
|
} elseif ( is_archive() || is_category() || is_tag() ) {
|
|
$critical_css = roi_get_archive_critical_css();
|
|
} else {
|
|
$critical_css = roi_get_default_critical_css();
|
|
}
|
|
|
|
// Cache for 24 hours
|
|
set_transient( $transient_key, $critical_css, DAY_IN_SECONDS );
|
|
|
|
return $critical_css;
|
|
}
|
|
|
|
/**
|
|
* Get current page type for caching
|
|
*
|
|
* @since 1.0.0
|
|
* @return string Page type identifier
|
|
*/
|
|
function roi_get_page_type() {
|
|
if ( is_front_page() ) {
|
|
return 'home';
|
|
} elseif ( is_single() ) {
|
|
return 'single';
|
|
} elseif ( is_archive() ) {
|
|
return 'archive';
|
|
} elseif ( is_search() ) {
|
|
return 'search';
|
|
} elseif ( is_404() ) {
|
|
return '404';
|
|
} else {
|
|
return 'page';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Critical CSS for Homepage
|
|
*
|
|
* @since 1.0.0
|
|
* @return string
|
|
*/
|
|
function roi_get_home_critical_css() {
|
|
return '
|
|
/* Reset and Base */
|
|
*,*::before,*::after{box-sizing:border-box}
|
|
body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;line-height:1.6;color:#333;background:#fff}
|
|
img{max-width:100%;height:auto;display:block}
|
|
a{color:#0066cc;text-decoration:none}
|
|
|
|
/* Header */
|
|
.site-header{background:#fff;border-bottom:1px solid #e5e5e5;position:sticky;top:0;z-index:1000}
|
|
.site-header .container{max-width:1200px;margin:0 auto;padding:1rem}
|
|
.site-branding{display:flex;align-items:center}
|
|
.site-title{margin:0;font-size:1.5rem;font-weight:700}
|
|
|
|
/* Hero Section */
|
|
.hero-section{padding:3rem 1rem;text-align:center;background:#f8f9fa}
|
|
.hero-title{font-size:2.5rem;margin:0 0 1rem;font-weight:700;line-height:1.2}
|
|
.hero-description{font-size:1.125rem;color:#666;max-width:600px;margin:0 auto}
|
|
|
|
/* Container */
|
|
.container{max-width:1200px;margin:0 auto;padding:0 1rem}
|
|
|
|
/* Featured Posts Grid */
|
|
.featured-posts{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:2rem;margin:2rem 0}
|
|
.post-card{background:#fff;border:1px solid #e5e5e5;border-radius:8px;overflow:hidden;transition:transform .2s}
|
|
.post-card:hover{transform:translateY(-4px)}
|
|
|
|
/* Skip to content */
|
|
.skip-link{position:absolute;left:-999px;top:0;background:#0066cc;color:#fff;padding:.5rem 1rem;text-decoration:none}
|
|
.skip-link:focus{left:0}
|
|
';
|
|
}
|
|
|
|
/**
|
|
* Critical CSS for Single Post/Page
|
|
*
|
|
* @since 1.0.0
|
|
* @return string
|
|
*/
|
|
function roi_get_single_critical_css() {
|
|
return '
|
|
/* Reset and Base */
|
|
*,*::before,*::after{box-sizing:border-box}
|
|
body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;line-height:1.6;color:#333;background:#fff}
|
|
img{max-width:100%;height:auto;display:block}
|
|
a{color:#0066cc;text-decoration:none}
|
|
h1,h2,h3,h4,h5,h6{margin:1.5rem 0 1rem;font-weight:700;line-height:1.3}
|
|
h1{font-size:2.5rem}
|
|
h2{font-size:2rem}
|
|
h3{font-size:1.5rem}
|
|
p{margin:0 0 1.5rem}
|
|
|
|
/* Header */
|
|
.site-header{background:#fff;border-bottom:1px solid #e5e5e5;position:sticky;top:0;z-index:1000}
|
|
.site-header .container{max-width:1200px;margin:0 auto;padding:1rem}
|
|
|
|
/* Article */
|
|
.entry-header{margin:2rem 0}
|
|
.entry-title{font-size:2.5rem;margin:0 0 1rem;line-height:1.2}
|
|
.entry-meta{color:#666;font-size:.875rem}
|
|
.entry-content{max-width:800px;margin:0 auto;font-size:1.125rem;line-height:1.8}
|
|
.featured-image{margin:2rem 0}
|
|
|
|
/* Container */
|
|
.container{max-width:1200px;margin:0 auto;padding:0 1rem}
|
|
|
|
/* Skip to content */
|
|
.skip-link{position:absolute;left:-999px;top:0;background:#0066cc;color:#fff;padding:.5rem 1rem;text-decoration:none}
|
|
.skip-link:focus{left:0}
|
|
';
|
|
}
|
|
|
|
/**
|
|
* Critical CSS for Archive Pages
|
|
*
|
|
* @since 1.0.0
|
|
* @return string
|
|
*/
|
|
function roi_get_archive_critical_css() {
|
|
return '
|
|
/* Reset and Base */
|
|
*,*::before,*::after{box-sizing:border-box}
|
|
body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;line-height:1.6;color:#333;background:#fff}
|
|
img{max-width:100%;height:auto;display:block}
|
|
a{color:#0066cc;text-decoration:none}
|
|
|
|
/* Header */
|
|
.site-header{background:#fff;border-bottom:1px solid #e5e5e5;position:sticky;top:0;z-index:1000}
|
|
.site-header .container{max-width:1200px;margin:0 auto;padding:1rem}
|
|
|
|
/* Archive Header */
|
|
.archive-header{padding:2rem 1rem;background:#f8f9fa;border-bottom:1px solid #e5e5e5}
|
|
.archive-title{margin:0;font-size:2rem;font-weight:700}
|
|
.archive-description{margin:1rem 0 0;color:#666;font-size:1.125rem}
|
|
|
|
/* Posts Grid */
|
|
.posts-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:2rem;margin:2rem 0}
|
|
.post-card{background:#fff;border:1px solid #e5e5e5;border-radius:8px;overflow:hidden}
|
|
|
|
/* Container */
|
|
.container{max-width:1200px;margin:0 auto;padding:0 1rem}
|
|
|
|
/* Skip to content */
|
|
.skip-link{position:absolute;left:-999px;top:0;background:#0066cc;color:#fff;padding:.5rem 1rem;text-decoration:none}
|
|
.skip-link:focus{left:0}
|
|
';
|
|
}
|
|
|
|
/**
|
|
* Critical CSS for Default Pages
|
|
*
|
|
* @since 1.0.0
|
|
* @return string
|
|
*/
|
|
function roi_get_default_critical_css() {
|
|
return '
|
|
/* Reset and Base */
|
|
*,*::before,*::after{box-sizing:border-box}
|
|
body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;line-height:1.6;color:#333;background:#fff}
|
|
img{max-width:100%;height:auto;display:block}
|
|
a{color:#0066cc;text-decoration:none}
|
|
|
|
/* Header */
|
|
.site-header{background:#fff;border-bottom:1px solid #e5e5e5;position:sticky;top:0;z-index:1000}
|
|
.site-header .container{max-width:1200px;margin:0 auto;padding:1rem}
|
|
|
|
/* Container */
|
|
.container{max-width:1200px;margin:0 auto;padding:0 1rem}
|
|
|
|
/* Content */
|
|
.site-content{min-height:50vh;padding:2rem 0}
|
|
|
|
/* Skip to content */
|
|
.skip-link{position:absolute;left:-999px;top:0;background:#0066cc;color:#fff;padding:.5rem 1rem;text-decoration:none}
|
|
.skip-link:focus{left:0}
|
|
';
|
|
}
|
|
|
|
/**
|
|
* Output Critical CSS inline in head
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
function roi_output_critical_css() {
|
|
if ( ! roi_is_critical_css_enabled() ) {
|
|
return;
|
|
}
|
|
|
|
$critical_css = roi_get_critical_css();
|
|
|
|
if ( empty( $critical_css ) ) {
|
|
return;
|
|
}
|
|
|
|
// Minify CSS (remove extra whitespace and newlines)
|
|
$critical_css = preg_replace( '/\s+/', ' ', $critical_css );
|
|
$critical_css = trim( $critical_css );
|
|
|
|
// Output inline critical CSS
|
|
echo '<style id="roi-critical-css">' . $critical_css . '</style>' . "\n";
|
|
}
|
|
add_action( 'wp_head', 'roi_output_critical_css', 1 );
|
|
|
|
/**
|
|
* Load main stylesheet asynchronously when critical CSS is enabled
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
function roi_async_main_stylesheet() {
|
|
if ( ! roi_is_critical_css_enabled() ) {
|
|
return;
|
|
}
|
|
|
|
// Dequeue main stylesheet to prevent render-blocking
|
|
wp_dequeue_style( 'roi-theme-style' );
|
|
|
|
// Enqueue with media="print" and onload to load asynchronously
|
|
wp_enqueue_style(
|
|
'roi-theme-style-async',
|
|
get_stylesheet_uri(),
|
|
array(),
|
|
ROI_VERSION,
|
|
'print'
|
|
);
|
|
|
|
// Add onload attribute to switch media to "all"
|
|
add_filter( 'style_loader_tag', 'roi_add_async_attribute', 10, 2 );
|
|
}
|
|
add_action( 'wp_enqueue_scripts', 'roi_async_main_stylesheet', 999 );
|
|
|
|
/**
|
|
* Add async loading attributes to stylesheet
|
|
*
|
|
* @since 1.0.0
|
|
* @param string $html The link tag for the enqueued style.
|
|
* @param string $handle The style's registered handle.
|
|
* @return string Modified link tag
|
|
*/
|
|
function roi_add_async_attribute( $html, $handle ) {
|
|
if ( 'roi-theme-style-async' !== $handle ) {
|
|
return $html;
|
|
}
|
|
|
|
// Add onload attribute to switch media to "all"
|
|
$html = str_replace(
|
|
"media='print'",
|
|
"media='print' onload=\"this.media='all'\"",
|
|
$html
|
|
);
|
|
|
|
// Add noscript fallback
|
|
$html .= '<noscript><link rel="stylesheet" href="' . get_stylesheet_uri() . '"></noscript>';
|
|
|
|
return $html;
|
|
}
|
|
|
|
/**
|
|
* Add Customizer setting for Critical CSS
|
|
*
|
|
* @since 1.0.0
|
|
* @param WP_Customize_Manager $wp_customize Theme Customizer object.
|
|
*/
|
|
function roi_critical_css_customizer( $wp_customize ) {
|
|
// Add Performance section
|
|
$wp_customize->add_section(
|
|
'roi_performance',
|
|
array(
|
|
'title' => __( 'Performance Optimization', 'roi-theme' ),
|
|
'priority' => 130,
|
|
)
|
|
);
|
|
|
|
// Critical CSS Enable/Disable
|
|
$wp_customize->add_setting(
|
|
'roi_enable_critical_css',
|
|
array(
|
|
'default' => false,
|
|
'sanitize_callback' => 'roi_sanitize_checkbox',
|
|
'transport' => 'refresh',
|
|
)
|
|
);
|
|
|
|
$wp_customize->add_control(
|
|
'roi_enable_critical_css',
|
|
array(
|
|
'label' => __( 'Enable Critical CSS', 'roi-theme' ),
|
|
'description' => __( 'Inline critical CSS and load main stylesheet asynchronously. This can improve Core Web Vitals but may cause a flash of unstyled content (FOUC). Test thoroughly before enabling in production.', 'roi-theme' ),
|
|
'section' => 'roi_performance',
|
|
'type' => 'checkbox',
|
|
)
|
|
);
|
|
}
|
|
add_action( 'customize_register', 'roi_critical_css_customizer' );
|
|
|
|
/**
|
|
* Clear critical CSS cache when theme is updated
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
function roi_clear_critical_css_cache() {
|
|
$page_types = array( 'home', 'single', 'archive', 'search', '404', 'page' );
|
|
|
|
foreach ( $page_types as $type ) {
|
|
delete_transient( 'roi_critical_css_' . $type );
|
|
}
|
|
}
|
|
add_action( 'after_switch_theme', 'roi_clear_critical_css_cache' );
|
|
add_action( 'customize_save_after', 'roi_clear_critical_css_cache' );
|
|
|
|
/**
|
|
* Output Hero Section Critical CSS
|
|
*
|
|
* This CSS is ALWAYS injected in the <head> to prevent CLS (Cumulative Layout Shift)
|
|
* by reserving space for the hero section BEFORE it renders.
|
|
*
|
|
* The min-height values match the actual rendered height (~260px on desktop)
|
|
* to prevent any layout shift when the dynamic CSS loads.
|
|
*
|
|
* @since 1.0.21
|
|
* @see Public/Hero/Infrastructure/Ui/HeroRenderer.php
|
|
*/
|
|
function roi_output_hero_critical_css() {
|
|
// Only output on single posts where hero is displayed
|
|
if ( ! is_single() ) {
|
|
return;
|
|
}
|
|
|
|
// Check if hero is enabled (read from database)
|
|
$is_enabled = roi_get_component_setting( 'hero', 'visibility', 'is_enabled', true );
|
|
if ( ! $is_enabled ) {
|
|
return;
|
|
}
|
|
|
|
$critical_css = '
|
|
/* Hero Critical CSS - Prevent CLS */
|
|
.hero-section {
|
|
min-height: 260px;
|
|
background: linear-gradient(135deg, #1e3a5f 0%, #2c5282 100%);
|
|
padding: 3rem 0;
|
|
margin-bottom: 1.5rem;
|
|
box-sizing: border-box;
|
|
}
|
|
.hero-section__title {
|
|
min-height: 3.5rem;
|
|
margin-bottom: 0;
|
|
}
|
|
.hero-section__badge {
|
|
min-height: 36px;
|
|
display: inline-block;
|
|
vertical-align: middle;
|
|
}
|
|
@media (max-width: 767.98px) {
|
|
.hero-section {
|
|
min-height: 200px;
|
|
}
|
|
.hero-section__title {
|
|
min-height: 2.5rem;
|
|
}
|
|
}
|
|
/* APU Tables Critical CSS - Prevent table reflow */
|
|
.analisis table, .desglose table {
|
|
table-layout: fixed;
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
.analisis, .desglose {
|
|
overflow-x: auto;
|
|
-webkit-overflow-scrolling: touch;
|
|
}
|
|
';
|
|
|
|
// Minify CSS
|
|
$critical_css = preg_replace( '/\s+/', ' ', $critical_css );
|
|
$critical_css = trim( $critical_css );
|
|
|
|
echo '<style id="roi-hero-critical-css">' . $critical_css . '</style>' . "\n";
|
|
}
|
|
add_action( 'wp_head', 'roi_output_hero_critical_css', 2 );
|