Created
September 2, 2025 19:55
-
-
Save wpeasy/62d4e145a9e8f8ab301330c63315f1d4 to your computer and use it in GitHub Desktop.
ATF Custom Brand Colors
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * WPE Brand bootstrap (users_brand → Brand post → CSS vars) | |
| * | |
| * Rules: | |
| * - Post Type: "Brand" | |
| * - User profile meta: users_brand (int Brand post ID) | |
| * - Brand meta: | |
| * - brand_primary_color -> --brand-primary | |
| * - brand_secondary_color -> --brand-secondary | |
| * - brand_tertiary_color -> --brand-tertiary | |
| */ | |
| add_action( 'init', 'wpe_brand_load_users_brand_global' ); | |
| /** | |
| * Load the current user's Brand (by users_brand post ID) into $wpe_brand and emit CSS vars. | |
| * | |
| * @param int $user_id Optional specific user ID; defaults to current user. | |
| * @return array|null | |
| */ | |
| function wpe_brand_load_users_brand_global( $user_id = 0 ) { | |
| global $wpe_brand; | |
| $wpe_brand = null; | |
| // Resolve user | |
| if ( ! $user_id ) { | |
| $user_id = get_current_user_id(); | |
| } | |
| if ( ! $user_id ) { | |
| return null; | |
| } | |
| // Read the Brand post ID from user meta (users_brand) | |
| $brand_id = get_user_meta( $user_id, 'users_brand', true ); | |
| if ( $brand_id === '' || $brand_id === null ) { | |
| return null; | |
| } | |
| $brand_id = absint( $brand_id ); | |
| if ( ! $brand_id ) { | |
| return null; | |
| } | |
| // Load the Brand post (micro-cached) | |
| $brand_post = wpe_brand_find_brand_by_id( $brand_id ); | |
| if ( ! ( $brand_post instanceof WP_Post ) ) { | |
| return null; | |
| } | |
| // Core info | |
| $core = array( | |
| 'ID' => $brand_post->ID, | |
| 'post_type' => $brand_post->post_type, | |
| 'status' => $brand_post->post_status, | |
| 'slug' => $brand_post->post_name, | |
| 'title' => get_the_title( $brand_post ), | |
| 'permalink' => get_permalink( $brand_post ), | |
| ); | |
| // Post meta (normalized to key => value/array) | |
| $raw_meta = get_post_meta( $brand_post->ID ); | |
| $meta = array(); | |
| foreach ( $raw_meta as $key => $vals ) { | |
| $meta[ $key ] = count( $vals ) === 1 ? maybe_unserialize( $vals[0] ) : array_map( 'maybe_unserialize', $vals ); | |
| } | |
| // Optional: ACF can be heavy; only load if you need it somewhere else. | |
| $acf_fields = array(); // intentionally empty to avoid overhead | |
| $wpe_brand = array( | |
| 'core' => $core, | |
| 'acf' => $acf_fields, | |
| 'meta' => $meta, | |
| 'post' => $brand_post, | |
| ); | |
| // Emit CSS vars from Brand colors | |
| if ( function_exists( 'wpe_brand_parse_users_brand' ) ) { | |
| wpe_brand_parse_users_brand( $wpe_brand ); | |
| } else { | |
| wpe_brand_print_brand_colors_head( $meta ); | |
| } | |
| return $wpe_brand; | |
| } | |
| /** | |
| * Find a Brand post by ID, ensuring correct post_type/status. | |
| * Micro-cached per request. | |
| */ | |
| function wpe_brand_find_brand_by_id( int $brand_id ): ?WP_Post { | |
| static $cache = array(); | |
| if ( isset( $cache[ $brand_id ] ) ) { | |
| return $cache[ $brand_id ]; | |
| } | |
| $post = get_post( $brand_id ); | |
| if ( ! ( $post instanceof WP_Post ) ) { | |
| return $cache[ $brand_id ] = null; | |
| } | |
| // Ensure it is a published Brand | |
| if ( $post->post_type !== 'brand' || $post->post_status !== 'publish' ) { | |
| return $cache[ $brand_id ] = null; | |
| } | |
| return $cache[ $brand_id ] = $post; | |
| } | |
| /** | |
| * Hook point to parse the user's Brand structure and print CSS. | |
| */ | |
| function wpe_brand_parse_users_brand( array $wpe_brand ) { | |
| $meta = $wpe_brand['meta'] ?? array(); | |
| if ( empty( $meta ) ) { | |
| return; | |
| } | |
| wpe_brand_print_brand_colors_head( $meta ); | |
| } | |
| /** Normalize a HEX color (#RGB or #RRGGBB). */ | |
| function wpe_brand_normalize_hex_color( $color ): ?string { | |
| if ( function_exists( 'sanitize_hex_color' ) ) { | |
| return sanitize_hex_color( $color ) ?: null; | |
| } | |
| $color = trim( (string) $color ); | |
| if ( $color === '' ) return null; | |
| if ( $color[0] !== '#' ) $color = '#' . $color; | |
| if ( preg_match( '/^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$/', $color ) !== 1 ) { | |
| return null; | |
| } | |
| return strtolower( $color ); | |
| } | |
| /** Detect Bricks editor contexts to optionally add !important. */ | |
| function wpe_brand_is_bricks_editor(): bool { | |
| if ( function_exists( 'bricks_is_builder' ) && bricks_is_builder() ) { | |
| return true; | |
| } | |
| if ( isset( $_GET['bricks'] ) ) { | |
| $val = strtolower( (string) wp_unslash( $_GET['bricks'] ) ); | |
| if ( in_array( $val, array( 'run', 'builder', 'preview' ), true ) ) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| /** | |
| * Build a :root CSS block from Brand color meta. | |
| * | |
| * Maps: | |
| * - brand_primary_color -> --brand-primary | |
| * - brand_secondary_color -> --brand-secondary | |
| * - brand_tertiary_color -> --brand-tertiary | |
| * | |
| * Generates exactly 3 steps for -d-, -l-, and -t- series using color-mix in oklch. | |
| * Also emits --brand-*-text as either #000000 or #ffffff (higher WCAG contrast). | |
| * If $important is true, appends !important to each assignment (used in Bricks editor). | |
| */ | |
| function wpe_brand_parse_brand_colors( array $meta, bool $important = false ): string { | |
| $map = array( | |
| 'brand_primary_color' => 'primary', | |
| 'brand_secondary_color' => 'secondary', | |
| 'brand_tertiary_color' => 'tertiary', | |
| ); | |
| $var_prefix = apply_filters( 'wpe_brand/brand_color_prefix', '--brand-' ); | |
| $steps = apply_filters( 'wpe_brand/brand_color_steps', array( '95', '50', '5' ) ); // exactly 3 steps | |
| $imp = $important ? ' !important' : ''; | |
| $lines = array(); | |
| foreach ( $map as $meta_key => $slug ) { | |
| if ( empty( $meta[ $meta_key ] ) ) { | |
| continue; | |
| } | |
| $raw = $meta[ $meta_key ]; | |
| $val = is_array( $raw ) ? reset( $raw ) : $raw; // meta may be array from get_post_meta() | |
| $hex = wpe_brand_normalize_hex_color( $val ); | |
| if ( ! $hex ) { | |
| continue; | |
| } | |
| $base_var = $var_prefix . $slug; | |
| $lines[] = sprintf( '%s: %s%s;', $base_var, $hex, $imp ); | |
| // Decide best text color (#000000 or #ffffff) by contrast ratio | |
| $text_hex = wpe_brand_best_text_color_for_bg( $hex ); | |
| $lines[] = sprintf( '%s-text: %s%s;', $base_var, $text_hex, $imp ); | |
| // Derived series (dark, light, transparent) | |
| $idx = 1; | |
| foreach ( $steps as $keep ) { | |
| $keep = preg_replace( '/[^0-9.]/', '', (string) $keep ); | |
| if ( $keep === '' ) { | |
| continue; | |
| } | |
| $lines[] = sprintf( | |
| '%1$s-d-%2$d: color-mix(in oklch, var(%1$s) %3$s%%, black)%4$s;', | |
| $base_var, $idx, $keep, $imp | |
| ); | |
| $lines[] = sprintf( | |
| '%1$s-l-%2$d: color-mix(in oklch, var(%1$s) %3$s%%, white)%4$s;', | |
| $base_var, $idx, $keep, $imp | |
| ); | |
| $lines[] = sprintf( | |
| '%1$s-t-%2$d: color-mix(in oklch, var(%1$s) %3$s%%, transparent)%4$s;', | |
| $base_var, $idx, $keep, $imp | |
| ); | |
| $idx++; | |
| } | |
| } | |
| if ( empty( $lines ) ) return ''; | |
| return ":root{\n " . implode( "\n ", $lines ) . "\n}\n"; | |
| } | |
| /** Print the :root CSS inside <head> as the last wp_head action. */ | |
| function wpe_brand_print_brand_colors_head( array $meta ): void { | |
| $important = wpe_brand_is_bricks_editor(); | |
| $css = wpe_brand_parse_brand_colors( $meta, $important ); | |
| if ( $css === '' ) return; | |
| add_action( | |
| 'wp_head', | |
| static function () use ( $css ) { | |
| echo "\n<style id=\"wpe-brand-colors\">\n" . $css . "</style>\n"; | |
| }, | |
| PHP_INT_MAX | |
| ); | |
| } | |
| /** ---------- Contrast helpers (WCAG 2.x relative luminance) ---------- */ | |
| /** Expand #rgb or #rrggbb to [r,g,b] 0–255. */ | |
| function wpe_brand_hex_to_rgb( string $hex ): ?array { | |
| $hex = ltrim( strtolower( $hex ), '#' ); | |
| if ( strlen( $hex ) === 3 ) { | |
| $r = hexdec( str_repeat( $hex[0], 2 ) ); | |
| $g = hexdec( str_repeat( $hex[1], 2 ) ); | |
| $b = hexdec( str_repeat( $hex[2], 2 ) ); | |
| return array( $r, $g, $b ); | |
| } | |
| if ( strlen( $hex ) === 6 ) { | |
| $r = hexdec( substr( $hex, 0, 2 ) ); | |
| $g = hexdec( substr( $hex, 2, 2 ) ); | |
| $b = hexdec( substr( $hex, 4, 2 ) ); | |
| return array( $r, $g, $b ); | |
| } | |
| return null; | |
| } | |
| /** Convert 0–255 sRGB to linear (per WCAG). */ | |
| function wpe_brand_srgb_to_linear( float $c ): float { | |
| $c = $c / 255; | |
| return ( $c <= 0.03928 ) ? ( $c / 12.92 ) : pow( ( $c + 0.055 ) / 1.055, 2.4 ); | |
| } | |
| /** Relative luminance (0..1). */ | |
| function wpe_brand_relative_luminance( string $hex ): ?float { | |
| $rgb = wpe_brand_hex_to_rgb( $hex ); | |
| if ( ! $rgb ) return null; | |
| list( $r, $g, $b ) = $rgb; | |
| $R = wpe_brand_srgb_to_linear( $r ); | |
| $G = wpe_brand_srgb_to_linear( $g ); | |
| $B = wpe_brand_srgb_to_linear( $b ); | |
| return 0.2126 * $R + 0.7152 * $G + 0.0722 * $B; | |
| } | |
| /** Contrast ratio between two colors (>=1). */ | |
| function wpe_brand_contrast_ratio( string $hex1, string $hex2 ): ?float { | |
| $L1 = wpe_brand_relative_luminance( $hex1 ); | |
| $L2 = wpe_brand_relative_luminance( $hex2 ); | |
| if ( $L1 === null || $L2 === null ) return null; | |
| $light = max( $L1, $L2 ); | |
| $dark = min( $L1, $L2 ); | |
| return ( $light + 0.05 ) / ( $dark + 0.05 ); | |
| } | |
| /** Choose #000000 or #ffffff for best contrast on a background color. */ | |
| function wpe_brand_best_text_color_for_bg( string $bg_hex ): string { | |
| $bg_hex = wpe_brand_normalize_hex_color( $bg_hex ) ?: $bg_hex; | |
| $black = '#000000'; | |
| $white = '#ffffff'; | |
| $c_black = wpe_brand_contrast_ratio( $bg_hex, $black ); | |
| $c_white = wpe_brand_contrast_ratio( $bg_hex, $white ); | |
| // Default to black if equal or any nulls | |
| if ( $c_white !== null && $c_black !== null ) { | |
| return ( $c_white > $c_black ) ? $white : $black; | |
| } | |
| return $black; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment