/** * FLO — Betheme Child Theme * ------------------------------------------------------------------ * Arquitectura (según instrucciones del proyecto): * - CSS/JS globales encolados aquí (no en Theme Options de Betheme). * - Secciones del homepage servidas como HTML crudo vía shortcode * [flo_section name='01-hero'] → carga sections/01-hero.php. * Esto evita que el editor de Betheme sobreescriba nuestras funciones. * - Nav y footer inyectados con wp_body_open / wp_footer (PHP real, * menús WP y carrito en vivo de WooCommerce). * - Todas las clases con prefijo flo- para no chocar con .section, * .price, .cart, etc. de Betheme y WooCommerce. * - Imágenes servidas desde assets/ (no base64) para performance/SEO. * * IMPORTANTE sobre Muffin Builder: * Hay 2 formas de insertar HTML en Betheme: * (a) "Text/Column" dentro del editor de Betheme → Betheme puede * envolver/override el markup y romper nuestros hooks. * (b) "Code / Raw HTML" item de Muffin Builder → respeta el markup. * Usamos (b): en Muffin Builder se coloca UNA sola línea inerte * [flo_section name='01-hero'] * y el PHP carga el HTML real desde el archivo. Así el contenido * pesado vive en el theme (versionable) y Muffin sólo referencia. * ------------------------------------------------------------------ */ if ( ! defined( 'ABSPATH' ) ) { exit; } define( 'FLO_VER', '1.0.0' ); define( 'FLO_DIR', get_stylesheet_directory() ); define( 'FLO_URI', get_stylesheet_directory_uri() ); define( 'FLO_ASSETS', FLO_URI . '/assets' ); /* ================================================================== * 1. ENCOLADO DE CSS / JS GLOBALES * ================================================================== */ add_action( 'wp_enqueue_scripts', 'flo_enqueue_assets', 999 ); function flo_enqueue_assets() { // Versionado por filemtime → invalida caché al editar los archivos. $css_file = FLO_DIR . '/assets/css/flo-global.css'; $js_file = FLO_DIR . '/assets/js/flo-global.js'; $css_ver = file_exists( $css_file ) ? filemtime( $css_file ) : FLO_VER; $js_ver = file_exists( $js_file ) ? filemtime( $js_file ) : FLO_VER; // style.css del child (header) — SIN dependencias frágiles. wp_enqueue_style( 'flo-child', get_stylesheet_uri(), array(), FLO_VER ); // CSS global de FLO (todos los estilos del homepage). // SIN dependencia de handles de Betheme que podrían no existir. wp_enqueue_style( 'flo-global', FLO_ASSETS . '/css/flo-global.css', array(), $css_ver ); // Tipografías del brand (Michroma display + Inter body). wp_enqueue_style( 'flo-fonts', 'https://fonts.googleapis.com/css2?family=Michroma&family=Inter:wght@300;400;500;600&display=swap', array(), null ); // GSAP + ScrollTrigger — vendored localmente (sin CDN) por las // instrucciones: mantener archivos una vez aprobados. wp_enqueue_script( 'flo-gsap', FLO_ASSETS . '/js/gsap.min.js', array(), '3.12.5', true ); wp_enqueue_script( 'flo-scrolltrigger', FLO_ASSETS . '/js/ScrollTrigger.min.js', array( 'flo-gsap' ), '3.12.5', true ); // JS global de FLO (hero player, turntable, configuradores, bicolor). wp_enqueue_script( 'flo-global', FLO_ASSETS . '/js/flo-global.js', array( 'flo-gsap', 'flo-scrolltrigger' ), $js_ver, true ); // Pasa rutas de assets al JS (los frames y fotos se cargan por URL, // no embebidos, para no inflar el DOM). wp_localize_script( 'flo-global', 'FLO_CFG', array( 'assets' => FLO_ASSETS, 'bikes' => FLO_ASSETS . '/bikes', 'video' => FLO_ASSETS . '/video', 'heroFrames' => 75, // hero0000..hero0074.jpg 'ttFrames' => 97, // tt0000..tt0096.jpg 'ajax' => admin_url( 'admin-ajax.php' ), 'shop' => function_exists( 'wc_get_page_permalink' ) ? wc_get_page_permalink( 'shop' ) : home_url( '/tienda' ), ) ); } /* ================================================================== * 2. SHORTCODE DE SECCIÓN — [flo_section name='01-hero'] * Carga sections/01-hero.php como HTML crudo. * ================================================================== */ add_shortcode( 'flo_section', 'flo_render_section' ); function flo_render_section( $atts ) { $atts = shortcode_atts( array( 'name' => '' ), $atts, 'flo_section' ); $name = sanitize_file_name( $atts['name'] ); if ( ! $name ) { return ''; } $file = FLO_DIR . '/sections/' . $name . '.php'; if ( ! file_exists( $file ) ) { return current_user_can( 'manage_options' ) ? '' : ''; } ob_start(); include $file; // el PHP puede usar shortcodes de WooCommerce dentro return ob_get_clean(); } /* ================================================================== * 3. INYECTORES DE NAV Y FOOTER (wp_body_open / wp_footer) * PHP real: menús WP + carrito en vivo de WooCommerce. * ================================================================== */ add_action( 'wp_body_open', 'flo_inject_prelude', 1 ); function flo_inject_prelude() { $file = FLO_DIR . '/inc/prelude.php'; if ( file_exists( $file ) ) { include $file; } } add_action( 'wp_body_open', 'flo_inject_nav', 5 ); function flo_inject_nav() { $file = FLO_DIR . '/inc/nav.php'; if ( file_exists( $file ) ) { include $file; } } add_action( 'wp_footer', 'flo_inject_footer', 5 ); function flo_inject_footer() { $file = FLO_DIR . '/inc/footer.php'; if ( file_exists( $file ) ) { include $file; } // Contenedor de filtros SVG (liquid glass) — una sola vez, global. $svg = FLO_DIR . '/inc/glass-filters.php'; if ( file_exists( $svg ) ) { include $svg; } } /* ================================================================== * 4. MENÚS Y SOPORTE DE TEMA * ================================================================== */ add_action( 'after_setup_theme', 'flo_theme_setup' ); function flo_theme_setup() { register_nav_menus( array( 'flo_primary' => 'FLO — Menú principal', 'flo_footer' => 'FLO — Menú footer', ) ); add_theme_support( 'title-tag' ); add_theme_support( 'post-thumbnails' ); add_theme_support( 'woocommerce' ); } /* ================================================================== * 5. HELPERS WOOCOMMERCE PARA DATOS DINÁMICOS * Shortcodes propios que envuelven datos de producto para usar * dentro de las secciones (precio, nombre, imagen destacada…). * ================================================================== */ // [flo_product_price id="123"] → precio formateado del producto. add_shortcode( 'flo_product_price', function( $atts ) { $atts = shortcode_atts( array( 'id' => 0 ), $atts ); if ( ! function_exists( 'wc_get_product' ) ) { return ''; } $p = wc_get_product( (int) $atts['id'] ); return $p ? $p->get_price_html() : ''; } ); // [flo_product_name id="123"] add_shortcode( 'flo_product_name', function( $atts ) { $atts = shortcode_atts( array( 'id' => 0 ), $atts ); if ( ! function_exists( 'wc_get_product' ) ) { return ''; } $p = wc_get_product( (int) $atts['id'] ); return $p ? esc_html( $p->get_name() ) : ''; } ); // [flo_cart_count] → número de items en el carrito (para el nav). add_shortcode( 'flo_cart_count', function() { if ( ! function_exists( 'WC' ) || is_null( WC()->cart ) ) { return '0'; } return (string) WC()->cart->get_cart_contents_count(); } ); /* ================================================================== * 6. FRAGMENTO AJAX DEL CARRITO (contador en vivo del nav) * ================================================================== */ add_filter( 'woocommerce_add_to_cart_fragments', 'flo_cart_fragment' ); function flo_cart_fragment( $fragments ) { $count = ( function_exists( 'WC' ) && ! is_null( WC()->cart ) ) ? WC()->cart->get_cart_contents_count() : 0; ob_start(); echo '' . esc_html( $count ) . ''; $fragments['.flo-cart-count'] = ob_get_clean(); return $fragments; } /* ================================================================== * 7. DIAGNÓSTICO Y FALLBACK DE ASSETS * Si por alguna razón (modo Performance de Betheme, plugin de caché * agresivo, etc.) los assets encolados no aparecen, este bloque: * a) imprime un aviso en el admin si faltan los archivos, y * b) inyecta CSS/JS críticos directamente como respaldo. * ================================================================== */ // (a) Aviso en el admin si los archivos no existen. add_action( 'admin_notices', 'flo_assets_notice' ); function flo_assets_notice() { if ( ! current_user_can( 'manage_options' ) ) { return; } $css = FLO_DIR . '/assets/css/flo-global.css'; $js = FLO_DIR . '/assets/js/flo-global.js'; $missing = array(); if ( ! file_exists( $css ) ) { $missing[] = 'assets/css/flo-global.css'; } if ( ! file_exists( $js ) ) { $missing[] = 'assets/js/flo-global.js'; } if ( $missing ) { echo '

FLO: faltan archivos del tema: ' . esc_html( implode( ', ', $missing ) ) . '. Revisa que la carpeta assets/ se haya subido completa.

'; } } // (b) Fallback: inyección directa si el handle no se encoló. // Sólo actúa si detecta que 'flo-global' NO está en cola. add_action( 'wp_head', 'flo_fallback_css', 999 ); function flo_fallback_css() { if ( wp_style_is( 'flo-global', 'enqueued' ) || wp_style_is( 'flo-global', 'done' ) ) { return; // ya cargó por la vía normal } $css = FLO_DIR . '/assets/css/flo-global.css'; if ( file_exists( $css ) ) { echo "\n\n"; echo '' . "\n"; // fuentes también echo '' . "\n"; } } add_action( 'wp_footer', 'flo_fallback_js', 999 ); function flo_fallback_js() { if ( wp_script_is( 'flo-global', 'enqueued' ) || wp_script_is( 'flo-global', 'done' ) ) { return; // ya cargó } $js = FLO_DIR . '/assets/js/flo-global.js'; if ( ! file_exists( $js ) ) { return; } echo "\n\n"; // Config para el resolver de assets. echo '' . "\n"; echo '' . "\n"; echo '' . "\n"; echo '' . "\n"; } /* ================================================================== * 8. FALLBACK wp_body_open (Betheme viejo puede no dispararlo) * Si el tema no llama wp_body_open(), inyectamos prelude+nav * justo después de la etiqueta vía output buffering. * ================================================================== */ add_action( 'template_redirect', 'flo_maybe_buffer_body', 1 ); function flo_maybe_buffer_body() { // Sólo en el front, no en admin/ajax. if ( is_admin() ) { return; } // Marca si wp_body_open se disparó. add_action( 'wp_body_open', 'flo_mark_body_open', 0 ); // Al final, si NO se disparó, insertamos manualmente. ob_start( 'flo_body_open_filter' ); } function flo_mark_body_open() { if ( ! defined( 'FLO_BODY_OPEN_FIRED' ) ) { define( 'FLO_BODY_OPEN_FIRED', true ); } } function flo_body_open_filter( $html ) { // Si el hook ya corrió, no hacemos nada (evita duplicar). if ( defined( 'FLO_BODY_OPEN_FIRED' ) ) { return $html; } // Construir el markup de apertura (prelude + nav). ob_start(); $prelude = FLO_DIR . '/inc/prelude.php'; if ( file_exists( $prelude ) ) { include $prelude; } $nav = FLO_DIR . '/inc/nav.php'; if ( file_exists( $nav ) ) { include $nav; } $inject = ob_get_clean(); // Insertar justo después de la primera etiqueta . return preg_replace( '/(]*>)/i', '$1' . $inject, $html, 1 ); } https://flo-bicicletas.mx/wp-sitemap-posts-post-1.xmlhttps://flo-bicicletas.mx/wp-sitemap-posts-page-1.xmlhttps://flo-bicicletas.mx/wp-sitemap-taxonomies-category-1.xmlhttps://flo-bicicletas.mx/wp-sitemap-users-1.xml