(paygrab-style-…); the key colour is a set of CSS variables. * The same HTML is used for all three; only the CSS changes. * * @package PAYGRAB */ defined( 'ABSPATH' ) || exit; /** * Looks and key colour. */ class Paygrab_Styles { /** Fallback key colour (near black). */ const FALLBACK = '#222222'; /** * Values for the admin preview (not saved yet), set by Paygrab_Preview. * * @var array{style?:string,mode?:string,color?:string} */ public static $preview = array(); /** * Hooks. * * @return void */ public static function init() { add_filter( 'body_class', array( __CLASS__, 'body_class' ) ); } /** * The looks. * * @return array key => label */ public static function looks() { return array( 'standard' => __( 'Standard', 'paygrab' ), 'card' => __( 'Card', 'paygrab' ), 'minimal' => __( 'Minimal', 'paygrab' ), ); } /** * The look in use. * * @return string standard | card | minimal */ public static function look() { $look = self::$preview['style'] ?? (string) Paygrab_Settings::get( 'design_style' ); return isset( self::looks()[ $look ] ) ? $look : 'standard'; } /** * Add paygrab-style-… to . * * @param string[] $classes Classes. * @return string[] */ public static function body_class( $classes ) { $classes[] = 'paygrab-style-' . self::look(); return $classes; } /** * Clean a #rrggbb colour. * * @param string $color Colour. * @return string '' when it is not a colour. */ public static function hex( $color ) { $color = (string) sanitize_hex_color( trim( (string) $color ) ); if ( 4 === strlen( $color ) ) { $color = '#' . $color[1] . $color[1] . $color[2] . $color[2] . $color[3] . $color[3]; } return strtolower( $color ); } /** * The key colour of a TCD theme (or of another theme through the filter). * * @return string '' when unknown. */ public static function theme_color() { // Older TCD themes spell the function "desing". $reader = function_exists( 'get_design_plus_option' ) ? 'get_design_plus_option' : ( function_exists( 'get_desing_plus_option' ) ? 'get_desing_plus_option' : '' ); $color = $reader ? self::color_in_options( (array) $reader() ) : ''; /** * Filters the theme's key colour used when "Match the theme" is chosen. * * @param string $color #rrggbb, or '' when unknown (then near black is used). */ return self::hex( (string) apply_filters( 'paygrab_theme_key_color', $color ) ); } /** * The key colour in the options of a TCD theme. Older themes keep it as "pickedcolor1" without "#". * * @param array $options Theme options. * @return string #rrggbb, or '' when none is found. */ public static function color_in_options( array $options ) { foreach ( array( 'main_color', 'color1', 'key_color', 'primary_color', 'accent_color', 'pickedcolor1', 'pickedcolor' ) as $key ) { $value = trim( (string) ( $options[ $key ] ?? '' ) ); $value = preg_match( '/^(?:[0-9a-f]{3}|[0-9a-f]{6})$/i', $value ) ? '#' . $value : $value; if ( self::hex( $value ) ) { return self::hex( $value ); } } return ''; } /** * The key colour in use. * * @return string #rrggbb */ public static function accent() { $mode = self::$preview['mode'] ?? (string) Paygrab_Settings::get( 'key_color_mode' ); $color = 'custom' === $mode ? self::hex( self::$preview['color'] ?? (string) Paygrab_Settings::get( 'key_color' ) ) : self::theme_color(); return $color && ! self::too_light( $color ) ? $color : self::FALLBACK; } /** Below this contrast with white, a button cannot be told apart from a white page. */ const MIN_CONTRAST = 1.2; /** * Whether a colour is so light that a button in it disappears on a white page. * Such colours cannot be chosen; a theme colour like this falls back to near black. * * @param string $hex #rrggbb. * @return bool */ public static function too_light( $hex ) { return self::contrast( $hex, '#ffffff' ) < self::MIN_CONTRAST; } /** * Relative luminance (0 dark – 1 light). * * @param string $hex #rrggbb. * @return float */ private static function luminance( $hex ) { $rgb = array_map( static fn( $c ) => hexdec( $c ) / 255, str_split( ltrim( $hex, '#' ), 2 ) ); $rgb = array_map( static fn( $c ) => $c <= 0.03928 ? $c / 12.92 : ( ( $c + 0.055 ) / 1.055 ) ** 2.4, $rgb ); return 0.2126 * $rgb[0] + 0.7152 * $rgb[1] + 0.0722 * $rgb[2]; } /** * Text colour on the key colour: white, or near black when the key colour is so light * that white text is hard to read (contrast below 3:1). Strong colours such as orange keep * white text, as the themes themselves do. * * @param string $hex Key colour. * @return string */ public static function text_on( $hex ) { $white = 1.05 / ( self::luminance( $hex ) + 0.05 ); return $white >= 3 ? '#ffffff' : self::FALLBACK; } /** * Contrast ratio of two colours (1 – 21). * * @param string $a #rrggbb. * @param string $b #rrggbb. * @return float */ public static function contrast( $a, $b ) { $la = self::luminance( $a ); $lb = self::luminance( $b ); return ( max( $la, $lb ) + 0.05 ) / ( min( $la, $lb ) + 0.05 ); } /** * Darken a colour until it can be read as text on a light background (4.5:1). * * @param string $hex #rrggbb. * @param string $bg Background (#rrggbb). * @return string */ public static function readable( $hex, $bg ) { for ( $step = 0; $step <= 20; $step++ ) { $color = self::mix( $hex, -0.05 * $step ); if ( self::contrast( $color, $bg ) >= 4.5 ) { return $color; } } return self::FALLBACK; } /** * Mix a colour with white (amount > 0) or black (amount < 0). * * @param string $hex #rrggbb. * @param float $amount -1 … 1. * @return string */ public static function mix( $hex, $amount ) { $out = '#'; foreach ( str_split( ltrim( $hex, '#' ), 2 ) as $part ) { $c = hexdec( $part ); $c = $amount >= 0 ? $c + ( 255 - $c ) * $amount : $c * ( 1 + $amount ); $out .= str_pad( dechex( (int) round( max( 0, min( 255, $c ) ) ) ), 2, '0', STR_PAD_LEFT ); } return $out; } /** * CSS variables for the key colour. * * The key colour fills buttons (with --paygrab-accent-text on it). Where it is used as text * on a white or soft surface (second buttons, the price of "Card"), --paygrab-accent-ink and * --paygrab-accent-dark are darkened until they can be read, so light colours still work. * * @return string */ public static function css() { $accent = self::accent(); $soft = self::mix( $accent, 0.91 ); return sprintf( '.paygrab{--paygrab-accent:%1$s;--paygrab-accent-text:%2$s;--paygrab-accent-ink:%3$s;--paygrab-accent-dark:%4$s;--paygrab-accent-soft:%5$s;--paygrab-accent-faint:%6$s;--paygrab-accent-edge:%7$s;}', $accent, self::text_on( $accent ), self::readable( $accent, $soft ), self::readable( self::mix( $accent, -0.22 ), $soft ), $soft, self::mix( $accent, 0.95 ), // With a light key colour the soft surface is almost white: give the "Card" second button a thin edge in the key colour. self::contrast( $soft, '#ffffff' ) < 1.1 ? $accent : $soft ); } }