* 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 '' . "\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 .= ''; 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 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: 32px; display: inline-block; } @media (max-width: 767.98px) { .hero-section { min-height: 200px; } .hero-section__title { min-height: 2.5rem; } } '; // Minify CSS $critical_css = preg_replace( '/\s+/', ' ', $critical_css ); $critical_css = trim( $critical_css ); echo '' . "\n"; } add_action( 'wp_head', 'roi_output_hero_critical_css', 2 );