From 10080e57ec1350f600a0b68712cdf60cf000a5a1 Mon Sep 17 00:00:00 2001 From: castellon Date: Wed, 7 Jan 2026 14:34:09 +0100 Subject: [PATCH 1/6] Fluid typography --- assets/admin/icons/events.svg | 7 + assets/admin/icons/fluid-typography.svg | 9 + docs/FLUID-TYPOGRAPHY.md | 250 ++++++++++++++++++++++++ includes/Admin/Settings.php | 91 +++++++++ includes/Frontend/FluidTypography.php | 238 ++++++++++++++++++++++ includes/Plugin_Main.php | 3 + 6 files changed, 598 insertions(+) create mode 100644 assets/admin/icons/events.svg create mode 100644 assets/admin/icons/fluid-typography.svg create mode 100644 docs/FLUID-TYPOGRAPHY.md create mode 100644 includes/Frontend/FluidTypography.php diff --git a/assets/admin/icons/events.svg b/assets/admin/icons/events.svg new file mode 100644 index 0000000..18d4f8a --- /dev/null +++ b/assets/admin/icons/events.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/admin/icons/fluid-typography.svg b/assets/admin/icons/fluid-typography.svg new file mode 100644 index 0000000..4e40ac4 --- /dev/null +++ b/assets/admin/icons/fluid-typography.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/docs/FLUID-TYPOGRAPHY.md b/docs/FLUID-TYPOGRAPHY.md new file mode 100644 index 0000000..f630279 --- /dev/null +++ b/docs/FLUID-TYPOGRAPHY.md @@ -0,0 +1,250 @@ +# Fluid Typography Module + +## Description + +The Fluid Typography module automatically converts GeneratePress Pro's static typography settings (with separate Desktop, Tablet, and Mobile values) into modern fluid typography using CSS `clamp()` function. + +**Now supports ALL typography elements** - not just headings, but navigation, buttons, site title, footer, and everything else! + +## How it Works + +Instead of using media queries that cause abrupt "jumps" at specific breakpoints, this module generates smooth, responsive font sizes that scale fluidly between viewports. + +### Traditional Approach (Media Queries) +```css +h1 { + font-size: 20px; /* Mobile */ +} + +@media (max-width: 1024px) { + h1 { + font-size: 40px; /* Tablet */ + } +} + +@media (min-width: 1025px) { + h1 { + font-size: 80px; /* Desktop */ + } +} +``` +❌ **Problem**: Abrupt jumps at 768px and 1024px breakpoints + +### Fluid Typography Approach (clamp()) +```css +h1 { + font-size: clamp(20px, calc(20px + (80 - 20) * ((100vw - 320px) / 1120)), 80px) !important; +} +``` +✅ **Solution**: Smooth, gradual scaling from 320px to 1440px viewport + +## Features + +- ✅ **Universal support** - Works with ALL GeneratePress typography elements +- ✅ **Automatic detection** - Reads from GeneratePress dynamic CSS +- ✅ **Smooth transitions** - No jumps between breakpoints +- ✅ **Modern CSS** - Uses native `clamp()` function +- ✅ **Easy toggle** - Enable/disable in FrontBlocks settings +- ✅ **Zero configuration** - Works automatically with existing GeneratePress settings +- ✅ **Debug mode** - Troubleshooting tools included + +## Supported Elements + +The module automatically processes **ALL typography elements** configured in GeneratePress, including: + +- **Body text** - Main content text +- **Headings** - H1, H2, H3, H4, H5, H6 +- **Navigation** - Main navigation links, menu toggle, menu bar items +- **Buttons** - All button types (button, input[type="button"], submit, reset, .button, WP Block buttons) +- **Site branding** - Site title, site description/tagline +- **Header & Footer** - Top bar, footer widgets, site info +- **And any other element** with responsive font-size settings in GeneratePress + +The module intelligently detects which elements have different font sizes across devices and only applies fluid typography where needed. + +## Requirements + +- GeneratePress theme (or child theme) +- GeneratePress Pro plugin (with Typography module) +- Typography settings configured in GeneratePress Customizer + +## Usage + +### Basic Setup + +1. Go to **FrontBlocks Settings** in your WordPress admin +2. Find **"Enable Fluid Typography"** in the **Optional Features** section +3. Toggle it **ON** +4. Save settings + +That's it! The module will automatically: +- Read your GeneratePress dynamic CSS +- Detect all responsive typography elements +- Generate fluid `clamp()` rules +- Apply them to your site + +### Debug Mode + +To troubleshoot or verify the module is working: + +1. Visit your site with `?frbl_debug=1` at the end of the URL +2. View page source (Ctrl+U or Cmd+U) +3. Search for `\n"; + } + ); + } + + if ( ! empty( $fluid_css ) ) { + wp_add_inline_style( 'generate-style', $fluid_css ); + } + } + + /** + * Convert GeneratePress static CSS to fluid typography. + * + * @param string $css Dynamic CSS from GeneratePress. + * @return string Fluid typography CSS. + */ + private function convert_to_fluid_typography( $css ) { + $fluid_css = ''; + + // Typography selectors to process - starting with simple ones that definitely work. + $selectors = array( + 'body', // Body text (paragraphs inherit from this). + 'h1', // Heading 1. + 'h2', // Heading 2. + 'h3', // Heading 3. + 'h4', // Heading 4. + 'h5', // Heading 5. + 'h6', // Heading 6. + ); + + foreach ( $selectors as $selector ) { + $fluid_rule = $this->extract_and_convert_selector( $css, $selector ); + + if ( ! empty( $fluid_rule ) ) { + $fluid_css .= $fluid_rule . "\n"; + } + } + + return $fluid_css; + } + + /** + * Extract font sizes from CSS and convert to fluid typography. + * + * @param string $css CSS content. + * @param string $selector CSS selector (e.g., 'h1'). + * @return string Fluid CSS rule or empty string. + */ + private function extract_and_convert_selector( $css, $selector ) { + $sizes = array( + 'desktop' => null, + 'tablet' => null, + 'mobile' => null, + ); + + // Use different regex patterns depending on the selector. + // Body is in a multiple selector (body, button, input...), headings are standalone. + $is_multiple_selector = ( 'body' === $selector ); + + // Extract base/desktop font-size. + if ( $is_multiple_selector ) { + // Pattern for body: body, button, input{font-size:18px;} + $pattern = '/(?:^|,|\})\s*[^{]*\b' . preg_quote( $selector, '/' ) . '\b[^{]*\{[^}]*font-size:\s*([0-9.]+)(px|rem|em)/i'; + } else { + // Pattern for headings: h1{font-size:80px;} + $pattern = '/' . preg_quote( $selector, '/' ) . '\s*\{[^}]*font-size:\s*([0-9.]+)(px|rem|em)/i'; + } + + if ( preg_match( $pattern, $css, $matches ) ) { + $sizes['desktop'] = array( + 'value' => floatval( $matches[1] ), + 'unit' => $matches[2], + ); + } + + // Extract tablet font-size from @media (max-width: 1024px). + if ( $is_multiple_selector ) { + // Pattern for body in media query. + $pattern_tablet = '/@media[^{]*max-width:\s*1024px[^{]*\{[^@]*\b' . preg_quote( $selector, '/' ) . '\b[^{]*\{[^}]*font-size:\s*([0-9.]+)(px|rem|em)/is'; + } else { + // Pattern for headings in media query: @media (max-width: 1024px){h1{font-size:40px;}} + $pattern_tablet = '/@media[^{]*max-width:\s*1024px[^{]*\{[^}]*' . preg_quote( $selector, '/' ) . '\s*\{[^}]*font-size:\s*([0-9.]+)(px|rem|em)/is'; + } + + if ( preg_match( $pattern_tablet, $css, $matches ) ) { + $sizes['tablet'] = array( + 'value' => floatval( $matches[1] ), + 'unit' => $matches[2], + ); + } + + // Extract mobile font-size from @media (max-width: 768px). + if ( $is_multiple_selector ) { + // Pattern for body in media query. + $pattern_mobile = '/@media[^{]*max-width:\s*768px[^{]*\{[^@]*\b' . preg_quote( $selector, '/' ) . '\b[^{]*\{[^}]*font-size:\s*([0-9.]+)(px|rem|em)/is'; + } else { + // Pattern for headings in media query: @media (max-width:768px){h1{font-size:20px;}} + $pattern_mobile = '/@media[^{]*max-width:\s*768px[^{]*\{[^}]*' . preg_quote( $selector, '/' ) . '\s*\{[^}]*font-size:\s*([0-9.]+)(px|rem|em)/is'; + } + + if ( preg_match( $pattern_mobile, $css, $matches ) ) { + $sizes['mobile'] = array( + 'value' => floatval( $matches[1] ), + 'unit' => $matches[2], + ); + } + + // If we don't have enough data, skip. + if ( ! $sizes['desktop'] || ! $sizes['mobile'] ) { + return ''; + } + + // Ensure all units are the same. + if ( $sizes['desktop']['unit'] !== $sizes['mobile']['unit'] ) { + return ''; + } + + $min_size = $sizes['mobile']['value']; + $max_size = $sizes['desktop']['value']; + $unit = $sizes['desktop']['unit']; + + // Skip if same values. + if ( $min_size === $max_size ) { + return ''; + } + + // Generate fluid typography with clamp(). + // Viewport range: 320px (mobile) to 1440px (desktop). + $viewport_start = 320; + $viewport_end = 1440; + $viewport_diff = $viewport_end - $viewport_start; + + // Build clamp() formula. + $fluid_calc = sprintf( + 'calc(%1$s%2$s + (%3$s - %1$s) * ((100vw - %4$spx) / %5$s))', + $min_size, + $unit, + $max_size, + $viewport_start, + $viewport_diff + ); + + $clamp_rule = sprintf( + 'clamp(%1$s%2$s, %3$s, %4$s%2$s)', + $min_size, + $unit, + $fluid_calc, + $max_size + ); + + // Generate CSS rule with appropriate specificity. + if ( in_array( $selector, array( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ) ) ) { + // For headings, use high specificity to ensure they always win over body classes. + $rule = sprintf( "body %s,\nbody %s.gb-headline {\n\tfont-size: %s !important;\n}", $selector, $selector, $clamp_rule ); + } else { + // For body, apply to body and paragraphs with GB classes. + $rule = sprintf( "%s {\n\tfont-size: %s !important;\n}", $selector, $clamp_rule ); + if ( 'body' === $selector ) { + $rule .= sprintf( "\np.gb-headline-text {\n\tfont-size: %s !important;\n}", $clamp_rule ); + } + } + + return $rule; + } +} diff --git a/includes/Plugin_Main.php b/includes/Plugin_Main.php index 85cc034..268bede 100644 --- a/includes/Plugin_Main.php +++ b/includes/Plugin_Main.php @@ -123,6 +123,9 @@ private function load_modules() { // Gravity Forms Inline Layout module. new Frontend\GravityFormsInline(); + + // Fluid Typography module (GeneratePress Pro integration). + new Frontend\FluidTypography(); } /** From fc18d3c455689dd1e14a229c58a9c3c8cbe8ec5f Mon Sep 17 00:00:00 2001 From: castellon Date: Wed, 7 Jan 2026 15:50:46 +0100 Subject: [PATCH 2/6] readme --- includes/Frontend/FluidTypography.php | 2 +- readme.txt | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/includes/Frontend/FluidTypography.php b/includes/Frontend/FluidTypography.php index 5a6ccfe..b27ec5c 100644 --- a/includes/Frontend/FluidTypography.php +++ b/includes/Frontend/FluidTypography.php @@ -151,7 +151,7 @@ private function extract_and_convert_selector( $css, $selector ) { // Pattern for body in media query. $pattern_tablet = '/@media[^{]*max-width:\s*1024px[^{]*\{[^@]*\b' . preg_quote( $selector, '/' ) . '\b[^{]*\{[^}]*font-size:\s*([0-9.]+)(px|rem|em)/is'; } else { - // Pattern for headings in media query: @media (max-width: 1024px){h1{font-size:40px;}} + $pattern_tablet = '/@media[^{]*max-width:\s*1024px[^{]*\{[^}]*' . preg_quote( $selector, '/' ) . '\s*\{[^}]*font-size:\s*([0-9.]+)(px|rem|em)/is'; } diff --git a/readme.txt b/readme.txt index 170f034..51e8987 100644 --- a/readme.txt +++ b/readme.txt @@ -70,6 +70,18 @@ Display a vertical progress bar on the right side of posts that fills up as user **Back Button:** Display a floating back button in the bottom left corner that allows users to navigate to the previous page. Enable it from the FrontBlocks settings page. +**Fluid Typography:** +Automatically converts GeneratePress Pro's static typography settings into modern fluid typography using CSS clamp(). Instead of abrupt font size changes at breakpoints, this creates smooth, gradual scaling from mobile (320px) to desktop (1440px). + +Supports all typography elements configured in GeneratePress: +- Body text and paragraphs (including GenerateBlocks headline elements) +- All headings (H1-H6) +- Each element maintains its own responsive values +- Zero configuration - automatically reads from GeneratePress dynamic CSS +- Smooth transitions across all viewport sizes without jumps + +Simply enable "Fluid Typography" in FrontBlocks settings, and all your responsive typography will scale smoothly between devices! + **Custom SVG Animations:** Add animated graphics to GenerateBlocks Shape blocks by importing JSON files. Supports two formats that are automatically detected: **Lottie/Bodymovin** (import JSON from After Effects or LottieFiles.com) and **Custom CSS** (SVG + @keyframes). @@ -117,6 +129,15 @@ More information in the [FrontBlocks PRO](https://close.technology/en/wordpress- == Changelog == +== n.e.x.t == +* Added: Fluid Typography - Automatically converts GeneratePress typography to smooth fluid scaling using CSS clamp(). +* Added: Support for all typography elements (body, h1-h6) with individual responsive values. +* Added: Smart detection of multi-selector CSS patterns (body, button, input, textarea). +* Added: Automatic conversion from static breakpoints to fluid viewport scaling (320px-1440px). +* Added: High specificity CSS to properly override GenerateBlocks inline styles. +* Added: Debug mode for Fluid Typography troubleshooting (?frbl_debug=1). +* Improved: Better CSS parsing for media queries and responsive font sizes. + == 1.3.1 == * Improved: Custom SVG Animations now uses file upload instead of textarea for importing JSON files. * Added: Download example JSON button for Custom SVG Animations feature. From 56e5e48b93efb5cc5fed56b3e5b41a91c0bbfafb Mon Sep 17 00:00:00 2001 From: castellon Date: Thu, 8 Jan 2026 09:17:48 +0100 Subject: [PATCH 3/6] Fix --- includes/Frontend/FluidTypography.php | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/includes/Frontend/FluidTypography.php b/includes/Frontend/FluidTypography.php index b27ec5c..3af4344 100644 --- a/includes/Frontend/FluidTypography.php +++ b/includes/Frontend/FluidTypography.php @@ -58,7 +58,7 @@ private function init_hooks() { public function enqueue_fluid_typography() { // Get GeneratePress dynamic CSS output. $dynamic_css = get_option( 'generate_dynamic_css_output', '' ); - + if ( empty( $dynamic_css ) ) { return; } @@ -103,7 +103,7 @@ private function convert_to_fluid_typography( $css ) { foreach ( $selectors as $selector ) { $fluid_rule = $this->extract_and_convert_selector( $css, $selector ); - + if ( ! empty( $fluid_rule ) ) { $fluid_css .= $fluid_rule . "\n"; } @@ -132,13 +132,11 @@ private function extract_and_convert_selector( $css, $selector ) { // Extract base/desktop font-size. if ( $is_multiple_selector ) { - // Pattern for body: body, button, input{font-size:18px;} $pattern = '/(?:^|,|\})\s*[^{]*\b' . preg_quote( $selector, '/' ) . '\b[^{]*\{[^}]*font-size:\s*([0-9.]+)(px|rem|em)/i'; } else { - // Pattern for headings: h1{font-size:80px;} $pattern = '/' . preg_quote( $selector, '/' ) . '\s*\{[^}]*font-size:\s*([0-9.]+)(px|rem|em)/i'; } - + if ( preg_match( $pattern, $css, $matches ) ) { $sizes['desktop'] = array( 'value' => floatval( $matches[1] ), @@ -151,10 +149,9 @@ private function extract_and_convert_selector( $css, $selector ) { // Pattern for body in media query. $pattern_tablet = '/@media[^{]*max-width:\s*1024px[^{]*\{[^@]*\b' . preg_quote( $selector, '/' ) . '\b[^{]*\{[^}]*font-size:\s*([0-9.]+)(px|rem|em)/is'; } else { - $pattern_tablet = '/@media[^{]*max-width:\s*1024px[^{]*\{[^}]*' . preg_quote( $selector, '/' ) . '\s*\{[^}]*font-size:\s*([0-9.]+)(px|rem|em)/is'; } - + if ( preg_match( $pattern_tablet, $css, $matches ) ) { $sizes['tablet'] = array( 'value' => floatval( $matches[1] ), @@ -167,10 +164,9 @@ private function extract_and_convert_selector( $css, $selector ) { // Pattern for body in media query. $pattern_mobile = '/@media[^{]*max-width:\s*768px[^{]*\{[^@]*\b' . preg_quote( $selector, '/' ) . '\b[^{]*\{[^}]*font-size:\s*([0-9.]+)(px|rem|em)/is'; } else { - // Pattern for headings in media query: @media (max-width:768px){h1{font-size:20px;}} $pattern_mobile = '/@media[^{]*max-width:\s*768px[^{]*\{[^}]*' . preg_quote( $selector, '/' ) . '\s*\{[^}]*font-size:\s*([0-9.]+)(px|rem|em)/is'; } - + if ( preg_match( $pattern_mobile, $css, $matches ) ) { $sizes['mobile'] = array( 'value' => floatval( $matches[1] ), @@ -222,7 +218,7 @@ private function extract_and_convert_selector( $css, $selector ) { ); // Generate CSS rule with appropriate specificity. - if ( in_array( $selector, array( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ) ) ) { + if ( in_array( $selector, array( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ), true ) ) { // For headings, use high specificity to ensure they always win over body classes. $rule = sprintf( "body %s,\nbody %s.gb-headline {\n\tfont-size: %s !important;\n}", $selector, $selector, $clamp_rule ); } else { @@ -232,7 +228,7 @@ private function extract_and_convert_selector( $css, $selector ) { $rule .= sprintf( "\np.gb-headline-text {\n\tfont-size: %s !important;\n}", $clamp_rule ); } } - + return $rule; } } From 133b2e6cda7aa4211e53e6fe1206dc5a84a8c72c Mon Sep 17 00:00:00 2001 From: castellon Date: Thu, 8 Jan 2026 09:25:36 +0100 Subject: [PATCH 4/6] Fix lint --- includes/Admin/Settings.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/includes/Admin/Settings.php b/includes/Admin/Settings.php index 6d57972..7f16f1b 100644 --- a/includes/Admin/Settings.php +++ b/includes/Admin/Settings.php @@ -720,10 +720,10 @@ private function render_debug_section() { return; } - // Get GeneratePress settings. - $gp_settings = get_option( 'generate_settings', array() ); - - // Filter only font-related settings. + // Get GeneratePress settings. + $gp_settings = get_option( 'generate_settings', array() ); + + // Filter only font-related settings. $font_settings = array(); foreach ( $gp_settings as $key => $value ) { if ( strpos( $key, 'font' ) !== false || strpos( $key, 'heading' ) !== false ) { From 2dca170bc95ebf2cfa54f52bf4b5095435523241 Mon Sep 17 00:00:00 2001 From: castellon Date: Thu, 8 Jan 2026 09:27:47 +0100 Subject: [PATCH 5/6] Fix whitespace --- includes/Admin/Settings.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/includes/Admin/Settings.php b/includes/Admin/Settings.php index 7f16f1b..7ebff45 100644 --- a/includes/Admin/Settings.php +++ b/includes/Admin/Settings.php @@ -717,13 +717,13 @@ private function render_debug_section() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! $enabled || ! isset( $_GET['frbl_debug_typography'] ) ) { - return; - } + return; + } - // Get GeneratePress settings. - $gp_settings = get_option( 'generate_settings', array() ); + // Get GeneratePress settings. + $gp_settings = get_option( 'generate_settings', array() ); - // Filter only font-related settings. + // Filter only font-related settings. $font_settings = array(); foreach ( $gp_settings as $key => $value ) { if ( strpos( $key, 'font' ) !== false || strpos( $key, 'heading' ) !== false ) { From 1d1bb7947b769988d7f11bd008dd68b9233419ef Mon Sep 17 00:00:00 2001 From: castellon Date: Thu, 8 Jan 2026 09:28:53 +0100 Subject: [PATCH 6/6] Fix tabs --- includes/Admin/Settings.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/Admin/Settings.php b/includes/Admin/Settings.php index 7ebff45..b5dd480 100644 --- a/includes/Admin/Settings.php +++ b/includes/Admin/Settings.php @@ -717,8 +717,8 @@ private function render_debug_section() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! $enabled || ! isset( $_GET['frbl_debug_typography'] ) ) { - return; - } + return; + } // Get GeneratePress settings. $gp_settings = get_option( 'generate_settings', array() );