diff --git a/Assets/Vendor/bootstrap-icons-subset.css b/Assets/Vendor/bootstrap-icons-subset.css new file mode 100644 index 00000000..20b2090d --- /dev/null +++ b/Assets/Vendor/bootstrap-icons-subset.css @@ -0,0 +1,127 @@ +/*! + * Bootstrap Icons Subset - ROI Theme + * 104 iconos de 2050 originales (5.1%) + * Generado automaticamente - NO EDITAR + */ +@font-face{ + font-display:swap; + font-family:bootstrap-icons; + src:url("fonts/bootstrap-icons-subset.woff2") format("woff2"), + url("fonts/bootstrap-icons-subset.woff") format("woff") +} +.bi::before,[class*=" bi-"]::before,[class^=bi-]::before{ + display:inline-block; + font-family:bootstrap-icons!important; + font-style:normal; + font-weight:400!important; + font-variant:normal; + text-transform:none; + line-height:1; + vertical-align:-.125em; + -webkit-font-smoothing:antialiased; + -moz-osx-font-smoothing:grayscale +} +.bi-arrow-counterclockwise::before{content:"\f117"} +.bi-arrow-down-circle::before{content:"\f119"} +.bi-arrow-right::before{content:"\f138"} +.bi-arrow-up::before{content:"\f148"} +.bi-arrows-angle-expand::before{content:"\f14a"} +.bi-arrows-expand::before{content:"\f14c"} +.bi-arrows-fullscreen::before{content:"\f14d"} +.bi-arrows-move::before{content:"\f14e"} +.bi-arrows-vertical::before{content:"\f698"} +.bi-aspect-ratio::before{content:"\f150"} +.bi-badge-ad::before{content:"\f161"} +.bi-bar-chart::before{content:"\f17e"} +.bi-bootstrap::before{content:"\f1a8"} +.bi-bounding-box::before{content:"\f1b6"} +.bi-box-arrow-up-right::before{content:"\f1c5"} +.bi-c-circle::before{content:"\f7db"} +.bi-calendar-check::before{content:"\f1e2"} +.bi-calendar3::before{content:"\f214"} +.bi-card-image::before{content:"\f226"} +.bi-card-text::before{content:"\f228"} +.bi-chat-dots::before{content:"\f24a"} +.bi-chat-dots-fill::before{content:"\f249"} +.bi-chat-quote::before{content:"\f255"} +.bi-chat-text::before{content:"\f267"} +.bi-check-circle::before{content:"\f26b"} +.bi-chevron-down::before{content:"\f282"} +.bi-clock::before{content:"\f293"} +.bi-code::before{content:"\f2c8"} +.bi-code-slash::before{content:"\f2c6"} +.bi-cursor::before{content:"\f2e3"} +.bi-display::before{content:"\f302"} +.bi-envelope::before{content:"\f32f"} +.bi-envelope-fill::before{content:"\f32c"} +.bi-envelope-paper::before{content:"\f73d"} +.bi-exclamation-octagon::before{content:"\f337"} +.bi-exclamation-triangle::before{content:"\f33b"} +.bi-eye::before{content:"\f341"} +.bi-facebook::before{content:"\f344"} +.bi-file-earmark-text::before{content:"\f38b"} +.bi-file-text::before{content:"\f3b9"} +.bi-filetype-css::before{content:"\f742"} +.bi-filetype-js::before{content:"\f74c"} +.bi-folder-fill::before{content:"\f3d1"} +.bi-fonts::before{content:"\f3da"} +.bi-gear::before{content:"\f3e5"} +.bi-geo-alt-fill::before{content:"\f3e7"} +.bi-globe::before{content:"\f3ee"} +.bi-graph-up::before{content:"\f3f2"} +.bi-grid::before{content:"\f3fc"} +.bi-grid-3x3-gap::before{content:"\f3f9"} +.bi-hand-index::before{content:"\f403"} +.bi-hourglass::before{content:"\f421"} +.bi-hourglass-split::before{content:"\f41f"} +.bi-image::before{content:"\f42a"} +.bi-info-circle::before{content:"\f431"} +.bi-input-cursor::before{content:"\f436"} +.bi-input-cursor-text::before{content:"\f435"} +.bi-instagram::before{content:"\f437"} +.bi-key::before{content:"\f44f"} +.bi-layout-sidebar::before{content:"\f45f"} +.bi-layout-text-window-reverse::before{content:"\f463"} +.bi-lightning::before{content:"\f46f"} +.bi-lightning-charge-fill::before{content:"\f46c"} +.bi-link::before{content:"\f471"} +.bi-link-45deg::before{content:"\f470"} +.bi-linkedin::before{content:"\f472"} +.bi-list::before{content:"\f479"} +.bi-list-nested::before{content:"\f474"} +.bi-list-ol::before{content:"\f475"} +.bi-list-ul::before{content:"\f478"} +.bi-magic::before{content:"\f675"} +.bi-megaphone::before{content:"\f484"} +.bi-megaphone-fill::before{content:"\f483"} +.bi-menu-button-wide::before{content:"\f489"} +.bi-mouse::before{content:"\f499"} +.bi-palette::before{content:"\f4b1"} +.bi-person::before{content:"\f4e1"} +.bi-person-lines-fill::before{content:"\f4db"} +.bi-phone::before{content:"\f4e7"} +.bi-pin::before{content:"\f4ed"} +.bi-power::before{content:"\f4ff"} +.bi-send-fill::before{content:"\f6b9"} +.bi-share::before{content:"\f52e"} +.bi-shield-check::before{content:"\f52f"} +.bi-shield-lock::before{content:"\f538"} +.bi-shield-x::before{content:"\f53e"} +.bi-slash-circle::before{content:"\f567"} +.bi-sliders::before{content:"\f56b"} +.bi-square::before{content:"\f584"} +.bi-star::before{content:"\f588"} +.bi-star-fill::before{content:"\f586"} +.bi-stars::before{content:"\f589"} +.bi-tablet::before{content:"\f5ae"} +.bi-tag::before{content:"\f5b0"} +.bi-tags::before{content:"\f5b2"} +.bi-telephone-fill::before{content:"\f5b4"} +.bi-text-center::before{content:"\f5c4"} +.bi-text-paragraph::before{content:"\f5c8"} +.bi-three-dots::before{content:"\f5d4"} +.bi-toggle-on::before{content:"\f5d6"} +.bi-twitter-x::before{content:"\f8db"} +.bi-type::before{content:"\f5f7"} +.bi-whatsapp::before{content:"\f618"} +.bi-x-circle::before{content:"\f623"} \ No newline at end of file diff --git a/Assets/Vendor/bootstrap-icons-subset.min.css b/Assets/Vendor/bootstrap-icons-subset.min.css new file mode 100644 index 00000000..34e56388 --- /dev/null +++ b/Assets/Vendor/bootstrap-icons-subset.min.css @@ -0,0 +1 @@ +@font-face{font-display:swap;font-family:bootstrap-icons;src:url("fonts/bootstrap-icons-subset.woff2") format("woff2"),url("fonts/bootstrap-icons-subset.woff") format("woff")}.bi::before,[class*=" bi-"]::before,[class^=bi-]::before{display:inline-block;font-family:bootstrap-icons!important;font-style:normal;font-weight:400!important;font-variant:normal;text-transform:none;line-height:1;vertical-align:-.125em;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.bi-arrow-counterclockwise::before{content:"\f117"}.bi-arrow-down-circle::before{content:"\f119"}.bi-arrow-right::before{content:"\f138"}.bi-arrow-up::before{content:"\f148"}.bi-arrows-angle-expand::before{content:"\f14a"}.bi-arrows-expand::before{content:"\f14c"}.bi-arrows-fullscreen::before{content:"\f14d"}.bi-arrows-move::before{content:"\f14e"}.bi-arrows-vertical::before{content:"\f698"}.bi-aspect-ratio::before{content:"\f150"}.bi-badge-ad::before{content:"\f161"}.bi-bar-chart::before{content:"\f17e"}.bi-bootstrap::before{content:"\f1a8"}.bi-bounding-box::before{content:"\f1b6"}.bi-box-arrow-up-right::before{content:"\f1c5"}.bi-c-circle::before{content:"\f7db"}.bi-calendar-check::before{content:"\f1e2"}.bi-calendar3::before{content:"\f214"}.bi-card-image::before{content:"\f226"}.bi-card-text::before{content:"\f228"}.bi-chat-dots::before{content:"\f24a"}.bi-chat-dots-fill::before{content:"\f249"}.bi-chat-quote::before{content:"\f255"}.bi-chat-text::before{content:"\f267"}.bi-check-circle::before{content:"\f26b"}.bi-chevron-down::before{content:"\f282"}.bi-clock::before{content:"\f293"}.bi-code::before{content:"\f2c8"}.bi-code-slash::before{content:"\f2c6"}.bi-cursor::before{content:"\f2e3"}.bi-display::before{content:"\f302"}.bi-envelope::before{content:"\f32f"}.bi-envelope-fill::before{content:"\f32c"}.bi-envelope-paper::before{content:"\f73d"}.bi-exclamation-octagon::before{content:"\f337"}.bi-exclamation-triangle::before{content:"\f33b"}.bi-eye::before{content:"\f341"}.bi-facebook::before{content:"\f344"}.bi-file-earmark-text::before{content:"\f38b"}.bi-file-text::before{content:"\f3b9"}.bi-filetype-css::before{content:"\f742"}.bi-filetype-js::before{content:"\f74c"}.bi-folder-fill::before{content:"\f3d1"}.bi-fonts::before{content:"\f3da"}.bi-gear::before{content:"\f3e5"}.bi-geo-alt-fill::before{content:"\f3e7"}.bi-globe::before{content:"\f3ee"}.bi-graph-up::before{content:"\f3f2"}.bi-grid::before{content:"\f3fc"}.bi-grid-3x3-gap::before{content:"\f3f9"}.bi-hand-index::before{content:"\f403"}.bi-hourglass::before{content:"\f421"}.bi-hourglass-split::before{content:"\f41f"}.bi-image::before{content:"\f42a"}.bi-info-circle::before{content:"\f431"}.bi-input-cursor::before{content:"\f436"}.bi-input-cursor-text::before{content:"\f435"}.bi-instagram::before{content:"\f437"}.bi-key::before{content:"\f44f"}.bi-layout-sidebar::before{content:"\f45f"}.bi-layout-text-window-reverse::before{content:"\f463"}.bi-lightning::before{content:"\f46f"}.bi-lightning-charge-fill::before{content:"\f46c"}.bi-link::before{content:"\f471"}.bi-link-45deg::before{content:"\f470"}.bi-linkedin::before{content:"\f472"}.bi-list::before{content:"\f479"}.bi-list-nested::before{content:"\f474"}.bi-list-ol::before{content:"\f475"}.bi-list-ul::before{content:"\f478"}.bi-magic::before{content:"\f675"}.bi-megaphone::before{content:"\f484"}.bi-megaphone-fill::before{content:"\f483"}.bi-menu-button-wide::before{content:"\f489"}.bi-mouse::before{content:"\f499"}.bi-palette::before{content:"\f4b1"}.bi-person::before{content:"\f4e1"}.bi-person-lines-fill::before{content:"\f4db"}.bi-phone::before{content:"\f4e7"}.bi-pin::before{content:"\f4ed"}.bi-power::before{content:"\f4ff"}.bi-send-fill::before{content:"\f6b9"}.bi-share::before{content:"\f52e"}.bi-shield-check::before{content:"\f52f"}.bi-shield-lock::before{content:"\f538"}.bi-shield-x::before{content:"\f53e"}.bi-slash-circle::before{content:"\f567"}.bi-sliders::before{content:"\f56b"}.bi-square::before{content:"\f584"}.bi-star::before{content:"\f588"}.bi-star-fill::before{content:"\f586"}.bi-stars::before{content:"\f589"}.bi-tablet::before{content:"\f5ae"}.bi-tag::before{content:"\f5b0"}.bi-tags::before{content:"\f5b2"}.bi-telephone-fill::before{content:"\f5b4"}.bi-text-center::before{content:"\f5c4"}.bi-text-paragraph::before{content:"\f5c8"}.bi-three-dots::before{content:"\f5d4"}.bi-toggle-on::before{content:"\f5d6"}.bi-twitter-x::before{content:"\f8db"}.bi-type::before{content:"\f5f7"}.bi-whatsapp::before{content:"\f618"}.bi-x-circle::before{content:"\f623"} \ No newline at end of file diff --git a/Assets/Vendor/create-icons-subset.py b/Assets/Vendor/create-icons-subset.py new file mode 100644 index 00000000..c41f2328 --- /dev/null +++ b/Assets/Vendor/create-icons-subset.py @@ -0,0 +1,281 @@ +#!/usr/bin/env python3 +""" +Bootstrap Icons Subset Generator +Genera CSS y fuente optimizados solo con los iconos usados en roi-theme +""" + +import re +import subprocess +import os + +# Directorio base +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + +# Iconos usados en roi-theme (extraidos con grep) +USED_ICONS = [ + "bi-arrow-counterclockwise", + "bi-arrow-down-circle", + "bi-arrow-right", + "bi-arrows-angle-expand", + "bi-arrows-expand", + "bi-arrows-fullscreen", + "bi-arrows-move", + "bi-arrows-vertical", + "bi-arrow-up", + "bi-aspect-ratio", + "bi-badge-ad", + "bi-bar-chart", + "bi-bootstrap", + "bi-bounding-box", + "bi-box-arrow-up-right", + "bi-calendar3", + "bi-calendar-check", + "bi-card-image", + "bi-card-text", + "bi-c-circle", + "bi-chat-dots", + "bi-chat-dots-fill", + "bi-chat-quote", + "bi-chat-text", + "bi-check-circle", + "bi-chevron-down", + "bi-clock", + "bi-code", + "bi-code-slash", + "bi-cursor", + "bi-display", + "bi-envelope", + "bi-envelope-fill", + "bi-envelope-paper", + "bi-exclamation-octagon", + "bi-exclamation-triangle", + "bi-eye", + "bi-facebook", + "bi-file-earmark-text", + "bi-file-text", + "bi-filetype-css", + "bi-filetype-js", + "bi-folder-fill", + "bi-fonts", + "bi-gear", + "bi-geo-alt-fill", + "bi-globe", + "bi-graph-up", + "bi-grid", + "bi-grid-3x3-gap", + "bi-hand-index", + "bi-hourglass", + "bi-hourglass-split", + "bi-image", + "bi-info-circle", + "bi-input-cursor", + "bi-input-cursor-text", + "bi-instagram", + "bi-key", + "bi-layout-sidebar", + "bi-layout-text-window-reverse", + "bi-lightning", + "bi-lightning-charge-fill", + "bi-link", + "bi-link-45deg", + "bi-linkedin", + "bi-list", + "bi-list-nested", + "bi-list-ol", + "bi-list-ul", + "bi-magic", + "bi-megaphone", + "bi-megaphone-fill", + "bi-menu-button-wide", + "bi-mouse", + "bi-palette", + "bi-person", + "bi-person-lines-fill", + "bi-phone", + "bi-pin", + "bi-power", + "bi-send-fill", + "bi-share", + "bi-shield-check", + "bi-shield-lock", + "bi-shield-x", + "bi-slash-circle", + "bi-sliders", + "bi-square", + "bi-star", + "bi-star-fill", + "bi-stars", + "bi-tablet", + "bi-tag", + "bi-tags", + "bi-telephone-fill", + "bi-text-center", + "bi-text-paragraph", + "bi-three-dots", + "bi-toggle-on", + "bi-twitter-x", + "bi-type", + "bi-whatsapp", + "bi-x-circle", +] + +def read_original_css(): + """Lee el CSS original de Bootstrap Icons""" + css_path = os.path.join(BASE_DIR, "bootstrap-icons.min.css") + with open(css_path, "r", encoding="utf-8") as f: + return f.read() + +def extract_unicode_codes(css_content, icons): + """Extrae los codigos unicode de los iconos usados""" + codes = {} + for icon in icons: + # Buscar .bi-nombre::before{content:"\XXXX"} + pattern = rf'\.{re.escape(icon)}::before\{{content:"\\([a-f0-9]+)"\}}' + match = re.search(pattern, css_content, re.IGNORECASE) + if match: + codes[icon] = match.group(1) + else: + print(f"WARNING: No se encontro codigo para {icon}") + return codes + +def generate_subset_css(codes): + """Genera el CSS subset con solo los iconos usados""" + css_lines = [] + + # Header + css_lines.append("/*!") + css_lines.append(" * Bootstrap Icons Subset - ROI Theme") + css_lines.append(f" * {len(codes)} iconos de 2050 originales ({len(codes)/2050*100:.1f}%)") + css_lines.append(" * Generado automaticamente - NO EDITAR") + css_lines.append(" */") + + # @font-face con font-display: swap + css_lines.append('@font-face{') + css_lines.append(' font-display:swap;') + css_lines.append(' font-family:bootstrap-icons;') + css_lines.append(' src:url("fonts/bootstrap-icons-subset.woff2") format("woff2"),') + css_lines.append(' url("fonts/bootstrap-icons-subset.woff") format("woff")') + css_lines.append('}') + + # Estilos base para .bi + css_lines.append('.bi::before,[class*=" bi-"]::before,[class^=bi-]::before{') + css_lines.append(' display:inline-block;') + css_lines.append(' font-family:bootstrap-icons!important;') + css_lines.append(' font-style:normal;') + css_lines.append(' font-weight:400!important;') + css_lines.append(' font-variant:normal;') + css_lines.append(' text-transform:none;') + css_lines.append(' line-height:1;') + css_lines.append(' vertical-align:-.125em;') + css_lines.append(' -webkit-font-smoothing:antialiased;') + css_lines.append(' -moz-osx-font-smoothing:grayscale') + css_lines.append('}') + + # Reglas para cada icono + for icon, code in sorted(codes.items()): + css_lines.append(f'.{icon}::before{{content:"\\{code}"}}') + + return '\n'.join(css_lines) + +def generate_minified_css(codes): + """Genera CSS minificado""" + parts = [] + + # @font-face + parts.append('@font-face{font-display:swap;font-family:bootstrap-icons;src:url("fonts/bootstrap-icons-subset.woff2") format("woff2"),url("fonts/bootstrap-icons-subset.woff") format("woff")}') + + # Estilos base + parts.append('.bi::before,[class*=" bi-"]::before,[class^=bi-]::before{display:inline-block;font-family:bootstrap-icons!important;font-style:normal;font-weight:400!important;font-variant:normal;text-transform:none;line-height:1;vertical-align:-.125em;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}') + + # Iconos + icon_rules = [f'.{icon}::before{{content:"\\{code}"}}' for icon, code in sorted(codes.items())] + parts.append(''.join(icon_rules)) + + return ''.join(parts) + +def create_font_subset(codes): + """Crea subset de la fuente usando pyftsubset""" + # Crear archivo con unicodes + unicodes = ','.join([f'U+{code}' for code in codes.values()]) + + # Paths + woff2_src = os.path.join(BASE_DIR, "fonts", "bootstrap-icons.woff2") + woff2_dst = os.path.join(BASE_DIR, "fonts", "bootstrap-icons-subset.woff2") + woff_src = os.path.join(BASE_DIR, "fonts", "bootstrap-icons.woff") + woff_dst = os.path.join(BASE_DIR, "fonts", "bootstrap-icons-subset.woff") + + # Crear subset woff2 + print(f"Creando subset woff2 con {len(codes)} glyphs...") + cmd_woff2 = [ + "pyftsubset", + woff2_src, + f"--unicodes={unicodes}", + f"--output-file={woff2_dst}", + "--flavor=woff2" + ] + subprocess.run(cmd_woff2, check=True) + + # Crear subset woff + print(f"Creando subset woff con {len(codes)} glyphs...") + cmd_woff = [ + "pyftsubset", + woff_src, + f"--unicodes={unicodes}", + f"--output-file={woff_dst}", + "--flavor=woff" + ] + subprocess.run(cmd_woff, check=True) + + # Mostrar tamanos + orig_size = os.path.getsize(woff2_src) / 1024 + new_size = os.path.getsize(woff2_dst) / 1024 + print(f"woff2: {orig_size:.1f} KB -> {new_size:.1f} KB ({(1-new_size/orig_size)*100:.1f}% reduccion)") + +def main(): + print("=== Bootstrap Icons Subset Generator ===\n") + + # 1. Leer CSS original + print("1. Leyendo CSS original...") + css_content = read_original_css() + + # 2. Extraer codigos unicode + print(f"2. Extrayendo codigos de {len(USED_ICONS)} iconos...") + codes = extract_unicode_codes(css_content, USED_ICONS) + print(f" Encontrados: {len(codes)} iconos") + + # 3. Generar CSS subset + print("3. Generando CSS subset...") + subset_css = generate_subset_css(codes) + css_path = os.path.join(BASE_DIR, "bootstrap-icons-subset.css") + with open(css_path, "w", encoding="utf-8") as f: + f.write(subset_css) + print(f" Guardado: bootstrap-icons-subset.css") + + # 4. Generar CSS minificado + print("4. Generando CSS minificado...") + min_css = generate_minified_css(codes) + min_path = os.path.join(BASE_DIR, "bootstrap-icons-subset.min.css") + with open(min_path, "w", encoding="utf-8") as f: + f.write(min_css) + orig_css_size = os.path.getsize(os.path.join(BASE_DIR, "bootstrap-icons.min.css")) / 1024 + new_css_size = os.path.getsize(min_path) / 1024 + print(f" CSS: {orig_css_size:.1f} KB -> {new_css_size:.1f} KB ({(1-new_css_size/orig_css_size)*100:.1f}% reduccion)") + + # 5. Crear font subset + print("5. Creando font subset...") + try: + create_font_subset(codes) + except Exception as e: + print(f" ERROR: {e}") + print(" Intenta ejecutar: pip install fonttools brotli") + return + + print("\n=== COMPLETADO ===") + print("Archivos generados:") + print(" - bootstrap-icons-subset.css (legible)") + print(" - bootstrap-icons-subset.min.css (minificado)") + print(" - fonts/bootstrap-icons-subset.woff2") + print(" - fonts/bootstrap-icons-subset.woff") + +if __name__ == "__main__": + main() diff --git a/Assets/Vendor/fonts/bootstrap-icons-subset.woff b/Assets/Vendor/fonts/bootstrap-icons-subset.woff new file mode 100644 index 00000000..19dd685f Binary files /dev/null and b/Assets/Vendor/fonts/bootstrap-icons-subset.woff differ diff --git a/Assets/Vendor/fonts/bootstrap-icons-subset.woff2 b/Assets/Vendor/fonts/bootstrap-icons-subset.woff2 new file mode 100644 index 00000000..c35614ba Binary files /dev/null and b/Assets/Vendor/fonts/bootstrap-icons-subset.woff2 differ diff --git a/Inc/enqueue-scripts.php b/Inc/enqueue-scripts.php index 4c01e1fd..9361a55c 100644 --- a/Inc/enqueue-scripts.php +++ b/Inc/enqueue-scripts.php @@ -46,12 +46,13 @@ function roi_enqueue_bootstrap() { 'all' ); - // Bootstrap Icons CSS - LOCAL (Issue #135: CORS bloqueaba CDN) + // Bootstrap Icons CSS - SUBSET OPTIMIZADO (Fase 4.1 PageSpeed) + // Original: 211 KB (2050 iconos) -> Subset: 13 KB (104 iconos) = 94% reduccion wp_enqueue_style( 'bootstrap-icons', - get_template_directory_uri() . '/Assets/Vendor/bootstrap-icons.min.css', + get_template_directory_uri() . '/Assets/Vendor/bootstrap-icons-subset.min.css', array('roi-bootstrap'), - '1.11.3', + ROI_VERSION, 'all' );