block into wp_admin_head that overrides the key * WordPress admin CSS custom properties and selectors, applying the user's * chosen primary/secondary colours across the entire admin interface. * * Design decisions: * - Global scope: one colour scheme for all users (simplest, most useful for agencies). * - CSS custom property override: targets --wp-admin-theme-color and friends so * it works with any WP version that uses the variable system (5.3+). * - Hard selector fallbacks: for older WP and themes that don't use vars. * - Does NOT disable WP's built-in colour scheme selector - it simply wins via * higher specificity / later cascade order. * - WCAG luminance: luminance is computed server-side and passed to JS for * the auto-contrast suggestion, keeping PHP as source of truth. * * Option keys (all in admbud_colours_group): * admbud_colours_enabled - '0'|'1' master toggle * admbud_colours_primary - hex e.g. #2563eb * admbud_colours_secondary - hex e.g. #1d4ed8 (hover / active) * admbud_colours_menu_text - hex e.g. #ffffff * admbud_colours_menu_bg - hex e.g. #1e293b (sidebar background) * * @package Admbud */ namespace Admbud; if ( ! defined( 'ABSPATH' ) ) { exit; } class Colours { // -- Defaults ------------------------------------------------------------- // Single source of truth for all default colours. // Referenced by class-settings.php (register_settings, activation hook) // so changing a value here is the only edit needed across the entire plugin. // // **Default preset slug** — when the Colours module is enabled on a fresh // install, the option values seeded by Settings::defaults() match the // preset named here. To change AB's out-of-the-box palette, change this // slug AND make sure the corresponding values in Settings::defaults() // match the new preset's `values` array in Settings::get_colour_presets(). // (A future refactor could derive Settings::defaults() programmatically // from this slug — left as a TODO to keep the WP.org resubmit scope small.) // // The constants below (DEFAULT_PRIMARY etc.) are now used only as PHP-side // fallbacks when an option read returns empty (e.g. after a reset). The // admin chrome itself no longer falls back to these — when the Colours // module is *off*, [tokens.css]'s --ab-primary falls back to WordPress // core's --wp-admin-theme-color (the user's chosen admin colour scheme), // not to AB's brand purple. Keep the constants in sync with the named // default preset so the two layers don't diverge. const DEFAULT_PRESET_SLUG = 'violety'; const DEFAULT_PRIMARY = '#7c3aed'; // Violet 600 - primary accent const DEFAULT_SECONDARY = '#6d28d9'; // Violet 700 - hover / active const DEFAULT_HOVER_BG = '#6d28d9'; // Violet 700 - menu item hover background const DEFAULT_ACTIVE_BG = '#7c3aed'; // Primary - active menu item background const DEFAULT_MENU_TEXT = '#ede9fe'; // Lavender 100 - sidebar text const DEFAULT_ACTIVE_TEXT = '#ffffff'; // Pure white - active item text (max contrast) const DEFAULT_MENU_BG = '#1e1b2e'; // Deep purple-slate - sidebar bg const DEFAULT_SEP_COLOR = '#5600ed'; // Vivid violet - separator line const DEFAULT_SIDEBAR_GRAD_FROM = '#2e1065'; // Deep violet - gradient start const DEFAULT_SIDEBAR_GRAD_TO = '#1e1b2e'; // Deep purple-slate - gradient end const DEFAULT_SIDEBAR_GRAD_DIR = 'to top'; // Sidebar gradient direction // Submenu / flyout defaults (sidebar) const DEFAULT_SUBMENU_BG = '#7c3aed'; // Primary - flyout background const DEFAULT_SUBMENU_HOVER_BG = '#7c3aed'; // Violet 600 - flyout hover bg const DEFAULT_SUBMENU_HOVER_TEXT = '#ede9fe'; // Lavender 100 - flyout hover text const DEFAULT_SUBMENU_ACTIVE_BG = '#7c3aed'; // Violet 700 - submenu active bg const DEFAULT_SUBMENU_ACTIVE_TEXT = '#ffffff'; // White - submenu active text // Page defaults - Login / Coming Soon / Maintenance share the same palette // so the whole plugin feels like one coherent product out of the box. const DEFAULT_PAGE_BG = '#1e1b2e'; // Same as menu bg - dark, branded const DEFAULT_PAGE_TEXT = '#ede9fe'; // Same as menu text - high contrast const DEFAULT_PAGE_MESSAGE = '#c4b5fd'; // Violet 300 - softer than heading // Login / maintenance page background type defaults const DEFAULT_LOGIN_BG_TYPE = 'solid'; // Login: solid colour const DEFAULT_CS_BG_TYPE = 'gradient'; // Coming Soon: gradient const DEFAULT_MAINT_BG_TYPE = 'gradient'; // Maintenance: gradient // -- Status / semantic colours --------------------------------------------- // Used for admin bar pills, badges, and notice elements. // Changing a value here updates every usage across the plugin automatically. // Coming Soon - amber (warning / pending) const COLOR_COMING_SOON = '#f59e0b'; // Amber 400 const COLOR_COMING_SOON_HOVER = '#d97706'; // Amber 500 // Maintenance - red (danger / offline) const COLOR_MAINTENANCE = '#ef4444'; // Red 500 const COLOR_MAINTENANCE_HOVER = '#dc2626'; // Red 600 // Noindex / Search engines blocked - violet (matches brand, informational) const COLOR_NOINDEX = '#8b5cf6'; // Violet 500 const COLOR_NOINDEX_HOVER = '#7c3aed'; // Violet 600 // -- WP default colours used as fallbacks for Content tokens -------------- const WP_CONTENT_BG = '#f0f0f1'; // #wpbody-content default const WP_HEADING_TEXT = '#1d2327'; // WP h1/h2 colour const WP_BODY_TEXT = '#3c434a'; // WP body text const WP_LINK = '#2271b1'; // WP link blue const WP_TABLE_ROW_BG = '#ffffff'; // WP table row const WP_TABLE_ROW_ALT_BG = '#f6f7f7'; // WP alternating row const WP_TABLE_BORDER = '#c3c4c7'; // WP table borders const WP_INPUT_BG = '#ffffff'; // WP input background const WP_INPUT_BORDER = '#8c8f94'; // WP input border const WP_BTN_SECONDARY_BG = '#f6f7f7'; // WP button-secondary bg const WP_POSTBOX_BG = '#ffffff'; // WP postbox/card bg const WP_POSTBOX_HEADER_BG = '#f6f7f7'; // WP postbox header bg const WP_POSTBOX_BORDER = '#c3c4c7'; // WP postbox border const WP_TABLE_ROW_TEXT = '#3c434a'; // WP table body text const WP_TABLE_ROW_SEPARATOR = '#e1e1e1'; // WP table row separator line // Success - green const COLOR_SUCCESS = '#16a34a'; // Green 600 // Error - red const COLOR_ERROR = '#dc2626'; // Red 600 // Warning - amber const COLOR_WARNING = '#f59e0b'; // Amber 400 // -- Singleton ------------------------------------------------------------- private static ?Colours $instance = null; public static function get_instance(): Colours { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } private function __construct() { add_action( 'admin_enqueue_scripts', [ $this, 'inject_css' ] ); add_action( 'login_enqueue_scripts', [ $this, 'inject_login_css' ] ); // Front-end admin bar: hook via wp_enqueue_scripts (only fires on front-end, never in wp-admin). add_action( 'wp_enqueue_scripts', [ $this, 'inject_frontend_css' ] ); // Bust the compiled CSS cache whenever any admbud_colours_* option is updated. add_action( 'updated_option', [ __CLASS__, 'maybe_bust_cache' ] ); // Flush stale colour CSS transients on plugin version change. // Covers updates (no activation hook) and fixes corrupted caches from // PHP deprecation warnings that may have been captured inside ob_start(). $last_ver = get_option( 'admbud_colours_css_version', '' ); if ( $last_ver !== ADMBUD_VERSION ) { // Delete current + any legacy transient keys. delete_transient( self::cache_key() ); if ( $last_ver ) { delete_transient( self::CSS_CACHE_PREFIX . $last_ver ); } // Repair opacity values zeroed by beta302-309 Settings API bug. // The opacity keys were registered in register_settings() but had no // form inputs, so WP sanitized absent values to 0 on every form save. if ( (int) get_option( 'admbud_colours_menu_bg_opacity', '100' ) === 0 ) { update_option( 'admbud_colours_menu_bg_opacity', '100' ); } if ( (int) get_option( 'admbud_colours_menu_text_opacity', '100' ) === 0 ) { update_option( 'admbud_colours_menu_text_opacity', '100' ); } update_option( 'admbud_colours_css_version', ADMBUD_VERSION, false ); } // Keep Admin Bar Flyout Bg in sync with Admin Bar Bg whenever it's saved. add_action( 'updated_option', function ( string $option, $old, $new ) { if ( $option === 'admbud_colours_adminbar_bg' && $new ) { admbud_update_option( 'admbud_colours_adminbar_submenu_bg', $new ); } }, 10, 3 ); } /** * Transient key for the compiled admin CSS. */ const CSS_CACHE_PREFIX = 'admbud_colours_css_'; /** * Delete the cached CSS when any colour option changes. * * Hooked to `updated_option` (fires for every update_option call). * Also called directly by the preset/palette AJAX handlers. */ private static function cache_key(): string { return self::CSS_CACHE_PREFIX . ADMBUD_VERSION; } public static function maybe_bust_cache( string $option = '' ): void { if ( $option === '' || str_starts_with( $option, 'admbud_colours_' ) || $option === 'admbud_css_exclusions' ) { delete_transient( self::cache_key() ); } } // ============================================================================ // CSS INJECTION - ADMIN // ============================================================================ public function inject_css(): void { // Check if current page is in the CSS exclusion list. $exclusions_raw = admbud_get_option( 'admbud_css_exclusions', '' ); $excluded = false; if ( $exclusions_raw !== '' ) { // Read-only display logic: these $_GET values determine which admin // screen is being viewed so the CSS exclusion list can match against // it. No state change, no database writes, no nonce required. Each // read is sanitize_key()'d at the source. $params_to_check = array_filter( array_unique( [ isset( $_GET['page'] ) ? sanitize_key( $_GET['page'] ) : '', // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput isset( $_GET['post_type'] ) ? sanitize_key( $_GET['post_type'] ) : '', // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput isset( $_GET['taxonomy'] ) ? sanitize_key( $_GET['taxonomy'] ) : '', // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput ] ) ); // Also check via get_current_screen() for reliability at admin_head time $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; if ( $screen ) { if ( $screen->id ) { $params_to_check[] = sanitize_key( $screen->id ); } if ( $screen->post_type ) { $params_to_check[] = sanitize_key( $screen->post_type ); } if ( $screen->taxonomy ) { $params_to_check[] = sanitize_key( $screen->taxonomy ); } if ( $screen->base ) { $params_to_check[] = sanitize_key( $screen->base ); } $params_to_check = array_unique( $params_to_check ); } $exclusion_list = array_filter( array_map( 'trim', explode( "\n", $exclusions_raw ) ) ); foreach ( $exclusion_list as $entry ) { $entry = sanitize_key( $entry ); if ( $entry === '' ) { continue; } foreach ( $params_to_check as $param ) { if ( $param === '' ) { continue; } // Exact match OR prefix match (e.g. "acf" matches "acf-field-group") if ( $param === $entry || str_starts_with( $param, $entry . '-' ) || str_starts_with( $param, $entry . '_' ) ) { $excluded = true; break 2; } } } } // Always generate. The previous transient cache short-circuited // before per-handle wp_add_inline_style() registrations inside // build_admin_css() ran, so on cache hit none of the colour CSS // was ever output - colours appeared on the first page load only // and disappeared on every subsequent navigation. The cache key // is kept (busted from class-upgrade.php on version change) so // existing transients are harmless. $this->build_admin_css( $excluded ); } /** * Strip wrappers from a captured CSS blob. * The cache predates the move to wp_add_inline_style() — historically the * blob included #i', '', $css ); } /** * Build the full admin colour CSS output. * * Extracted from inject_css() so the output can be captured and cached. * All get_option() calls and CSS generation live here. */ private function build_admin_css( bool $excluded = false ): void { $primary = $this->colour( 'admbud_colours_primary', self::DEFAULT_PRIMARY ); $secondary = $this->colour( 'admbud_colours_secondary', self::DEFAULT_SECONDARY ); $menu_text = $this->colour( 'admbud_colours_menu_text', self::DEFAULT_MENU_TEXT ); $menu_bg = $this->colour( 'admbud_colours_menu_bg', self::DEFAULT_MENU_BG ); // Apply opacity to menu colours (0-100 int stored as string). $menu_bg_opacity = (int) admbud_get_option( 'admbud_colours_menu_bg_opacity', '100' ); $menu_text_opacity = (int) admbud_get_option( 'admbud_colours_menu_text_opacity', '100' ); if ( $menu_bg_opacity < 100 ) { $menu_bg = $this->hex_to_rgba( $menu_bg, round( $menu_bg_opacity / 100, 2 ) ); } if ( $menu_text_opacity < 100 ) { $menu_text = $this->hex_to_rgba( $menu_text, round( $menu_text_opacity / 100, 2 ) ); } $primary_tint = $this->lighten( $primary, 0.88 ); // very light - table row bg $menu_bg_dark = $this->darken( $menu_bg, 0.12 ); // admin bar bg $menu_bg_light = $this->lighten( $menu_bg, 0.08 ); // hover state bg // Is the sidebar a light surface? Plugin SVG icons ship a light fill // tuned for WP's dark sidebar and disappear on a light one - used below // to force resting menu icons dark so they stay visible. The label/icon // *colour* is NOT transformed here: light palettes store an already // tinted-neutral menu_text (baked by the Auto Palette generator and the // light presets) so the colour picker reflects exactly what renders. // Only the icon opacity is derived, to land the neutral-black SVGs at // the same perceived darkness as that stored text colour. $sidebar_is_light = self::luminance( $menu_bg ) > 0.55; $svg_icon_opacity = 0.6; if ( $sidebar_is_light && preg_match( '/^#[0-9A-Fa-f]{3}(?:[0-9A-Fa-f]{3})?$/', $menu_text ) ) { $svg_icon_opacity = max( 0.55, min( 0.92, 1 - $this->lightness( $menu_text ) ) ); } $menu_text_dim = $this->dim_colour( $menu_text, 0.75 ); // dimmed label text $neutral_gray = '#646970'; // WP standard neutral for inputs/buttons // Sidebar gradient. $sidebar_gradient = admbud_get_option( 'admbud_colours_sidebar_gradient', '0' ) === '1'; $sidebar_grad_from = $this->colour( 'admbud_colours_sidebar_grad_from', self::DEFAULT_SIDEBAR_GRAD_FROM ); $sidebar_grad_to = $this->colour( 'admbud_colours_sidebar_grad_to', self::DEFAULT_SIDEBAR_GRAD_TO ); // New options: active item text, separator colour, body background, hover, SVG tint. $active_text_raw = admbud_get_option( 'admbud_colours_active_text', self::DEFAULT_ACTIVE_TEXT ); $active_text = $this->colour( 'admbud_colours_active_text', self::DEFAULT_ACTIVE_TEXT ); $sep_color_raw = admbud_get_option( 'admbud_colours_sep_color', '' ); $sep_color = $sep_color_raw ? $this->colour( 'admbud_colours_sep_color', $primary_tint ) : $primary_tint; $body_bg_raw = admbud_get_option( 'admbud_colours_body_bg', '' ); // -- Content area tokens (all optional - empty = WP default) ---------- $ct_heading_raw = admbud_get_option( 'admbud_colours_content_heading', '' ); $ct_text_raw = admbud_get_option( 'admbud_colours_content_text', '' ); $ct_link_raw = admbud_get_option( 'admbud_colours_content_link', '' ); $ct_link_hover_raw = admbud_get_option( 'admbud_colours_content_link_hover',''); $ct_tbl_hdr_bg_raw = admbud_get_option( 'admbud_colours_table_header_bg', '' ); $ct_tbl_hdr_txt_raw = admbud_get_option( 'admbud_colours_table_header_text', '' ); $ct_tbl_hdr_link_raw = admbud_get_option( 'admbud_colours_table_header_link', '' ); $ct_tbl_row_bg_raw = admbud_get_option( 'admbud_colours_table_row_bg', '' ); $ct_tbl_row_alt_raw = admbud_get_option( 'admbud_colours_table_row_alt_bg', '' ); $ct_tbl_row_hover_raw= admbud_get_option( 'admbud_colours_table_row_hover', '' ); $ct_tbl_border_raw = admbud_get_option( 'admbud_colours_table_border', '' ); $ct_tbl_action_raw = admbud_get_option( 'admbud_colours_table_action_link', '' ); $ct_tbl_title_raw = admbud_get_option( 'admbud_colours_table_title_link', '' ); $ct_input_bg_raw = admbud_get_option( 'admbud_colours_input_bg', '' ); $ct_input_border_raw = admbud_get_option( 'admbud_colours_input_border', '' ); $ct_input_focus_raw = admbud_get_option( 'admbud_colours_input_focus', '' ); $ct_btn_sec_bg_raw = admbud_get_option( 'admbud_colours_btn_secondary_bg', '' ); $ct_postbox_bg_raw = admbud_get_option( 'admbud_colours_postbox_bg', '' ); $ct_postbox_hdr_raw = admbud_get_option( 'admbud_colours_postbox_header_bg', '' ); $ct_postbox_bdr_raw = admbud_get_option( 'admbud_colours_postbox_border', '' ); $ct_postbox_txt_raw = admbud_get_option( 'admbud_colours_postbox_text', '' ); $ct_notice_bg_raw = admbud_get_option( 'admbud_colours_notice_bg', '' ); $ct_tbl_row_txt_raw = admbud_get_option( 'admbud_colours_table_row_text', '' ); $ct_tbl_alt_txt_raw = admbud_get_option( 'admbud_colours_table_row_alt_text', '' ); $ct_tbl_sep_raw = admbud_get_option( 'admbud_colours_table_row_separator', '' ); $ct_btn_primary_raw = admbud_get_option( 'admbud_colours_btn_primary_bg', '' ); $ct_btn_pri_txt_raw = admbud_get_option( 'admbud_colours_btn_primary_text', '' ); $ct_btn_pri_hvr_raw = admbud_get_option( 'admbud_colours_btn_primary_hover', '' ); // Admin bar text - explicit override. Empty = auto-derive from menu_text at 75% opacity. $adminbar_text_raw = admbud_get_option( 'admbud_colours_adminbar_text', '' ); // Admin bar background - explicit override. Empty = inherit menu_bg_dark (p5). $adminbar_bg_raw = admbud_get_option( 'admbud_colours_adminbar_bg', '' ); // Admin bar hover bg - explicit override. Empty = inherit primary (p1). $adminbar_hover_raw = admbud_get_option( 'admbud_colours_adminbar_hover_bg', '' ); // Admin bar submenu bg - explicit override. Empty = inherit menu_bg_dark (p5). $adminbar_sub_raw = admbud_get_option( 'admbud_colours_adminbar_submenu_bg', '' ); // Admin bar hover text - text colour on hover. Empty = inherit adminbar_text (p8). $adminbar_hover_text_raw = admbud_get_option( 'admbud_colours_adminbar_hover_text', '' ); // Admin bar submenu text - text in flyout panels. Empty = inherit adminbar_text (p8). $adminbar_sub_text_raw = admbud_get_option( 'admbud_colours_adminbar_sub_text', '' ); // Admin bar submenu hover text. Empty = inherit adminbar_text (p8). $adminbar_sub_hover_text_raw = admbud_get_option( 'admbud_colours_adminbar_sub_hover_text', '' ); // Submenu / flyout text - explicit override. Empty = inherit menu_text (p6). $submenu_text_raw = admbud_get_option( 'admbud_colours_submenu_text', '' ); // Hover text - text colour for top-level items on hover. Empty = inherit menu_text. $hover_text_raw = admbud_get_option( 'admbud_colours_hover_text', '' ); // Active parent text - text colour for the parent item whose submenu is open. Empty = inherit active_text. $active_parent_text_raw = admbud_get_option( 'admbud_colours_active_parent_text', '' ); // Sidebar submenu / flyout background - explicit override. Empty = inherit primary (p1). $submenu_bg_raw = admbud_get_option( 'admbud_colours_submenu_bg', '' ); // Sidebar submenu / flyout hover background. Empty = inherit secondary (p3). $submenu_hover_bg_raw = admbud_get_option( 'admbud_colours_submenu_hover_bg', '' ); // Sidebar submenu / flyout hover text. Empty = inherit submenu text (p14). $submenu_hover_text_raw = admbud_get_option( 'admbud_colours_submenu_hover_text', '' ); // Admin bar flyout hover background. Empty = inherit adminbar_hover_bg (p17). $adminbar_sub_hover_bg_raw = admbud_get_option( 'admbud_colours_adminbar_sub_hover_bg', '' ); // Drop shadow colour. Empty = rgba(0,0,0,0.35). $shadow_colour_raw = admbud_get_option( 'admbud_colours_shadow_colour', '' ); $p1 = esc_attr( $primary ); $p2 = esc_attr( $this->hex_to_rgb_triplet( $primary ) ); $hover_bg_raw = admbud_get_option( 'admbud_colours_hover_bg', '' ); $p3 = $hover_bg_raw ? esc_attr( $hover_bg_raw ) : esc_attr( $secondary ); $active_bg_raw = admbud_get_option( 'admbud_colours_active_bg', '' ); $p_active_bg = $active_bg_raw ? esc_attr( $active_bg_raw ) : esc_attr( $primary ); $sub_active_bg_raw = admbud_get_option( 'admbud_colours_submenu_active_bg', '' ); $sub_active_text_raw = admbud_get_option( 'admbud_colours_submenu_active_text', '' ); // Defaults: bg falls back to submenu hover bg, text falls back to active text (white). $sub_hover_bg_default = $this->colour( 'admbud_colours_submenu_hover_bg', $secondary ); $sub_hover_text_default = $this->colour( 'admbud_colours_submenu_hover_text', $menu_text ); $p_sub_active_bg = $sub_active_bg_raw ? esc_attr( $sub_active_bg_raw ) : esc_attr( $sub_hover_bg_default ); $p_sub_active_text = $sub_active_text_raw ? esc_attr( $sub_active_text_raw ) : esc_attr( $sub_hover_text_default ); $p4 = esc_attr( $menu_bg ); $p5 = esc_attr( $menu_bg_dark ); $p6 = esc_attr( $menu_text ); $p7 = esc_attr( $menu_bg_light ); $p8 = $adminbar_text_raw ? esc_attr( $this->colour( 'admbud_colours_adminbar_text', $menu_text_dim ) ) : esc_attr( $menu_text_dim ); $p9 = esc_attr( $primary_tint ); $p10 = esc_attr( $neutral_gray ); $p11 = esc_attr( $active_text ); // active item text colour $p12 = esc_attr( $sep_color ); // separator border colour $p14 = $submenu_text_raw ? esc_attr( $this->colour( 'admbud_colours_submenu_text', $menu_text ) ) : esc_attr( $menu_text ); // submenu/flyout text $p15 = $hover_text_raw ? esc_attr( $this->colour( 'admbud_colours_hover_text', $menu_text ) ) : esc_attr( $menu_text ); // hover text $p16 = $active_parent_text_raw ? esc_attr( $this->colour( 'admbud_colours_active_parent_text', $active_text ) ) : esc_attr( $active_text ); // active parent text $p17 = $adminbar_hover_raw ? esc_attr( $this->colour( 'admbud_colours_adminbar_hover_bg', $primary ) ) : esc_attr( $primary ); // admin bar hover bg $p18 = $adminbar_sub_raw ? esc_attr( $this->colour( 'admbud_colours_adminbar_submenu_bg', $menu_bg_dark ) ) : esc_attr( $menu_bg_dark ); // admin bar submenu bg $p19 = $adminbar_bg_raw ? esc_attr( $this->colour( 'admbud_colours_adminbar_bg', $menu_bg_dark ) ) : esc_attr( $menu_bg_dark ); // admin bar bg // Hover text default contrasts the hover bg (white on the dark accent // bar) instead of inheriting the neutral menu_text, which would render // the hover state the same grey as the resting state. Picker overrides. $p20 = $adminbar_hover_text_raw ? esc_attr( $this->colour( 'admbud_colours_adminbar_hover_text', $menu_text ) ) : esc_attr( self::suggest_text_colour( $adminbar_hover_raw ? $this->colour( 'admbud_colours_adminbar_hover_bg', $primary ) : $primary ) ); // admin bar hover text $p21 = $adminbar_sub_text_raw ? esc_attr( $this->colour( 'admbud_colours_adminbar_sub_text', $menu_text_dim ) ) : esc_attr( $menu_text_dim ); // admin bar submenu text $p22 = $adminbar_sub_hover_text_raw ? esc_attr( $this->colour( 'admbud_colours_adminbar_sub_hover_text', $menu_text ) ) : esc_attr( self::suggest_text_colour( $adminbar_sub_hover_bg_raw ? $this->colour( 'admbud_colours_adminbar_sub_hover_bg', $primary ) : ( $adminbar_sub_raw ? $this->colour( 'admbud_colours_adminbar_submenu_bg', $menu_bg_dark ) : $menu_bg_dark ) ) ); // admin bar flyout hover text - default contrasts the flyout hover bg, picker overrides $p23 = $submenu_bg_raw ? esc_attr( $this->colour( 'admbud_colours_submenu_bg', $primary ) ) : esc_attr( $primary ); // sidebar submenu/flyout bg $p24 = $submenu_hover_bg_raw ? esc_attr( $this->colour( 'admbud_colours_submenu_hover_bg', $secondary ) ) : esc_attr( $secondary ); // sidebar submenu hover bg $p25 = $submenu_hover_text_raw ? esc_attr( $this->colour( 'admbud_colours_submenu_hover_text', $menu_text ) ) : esc_attr( $menu_text ); // sidebar submenu hover text $p26 = $adminbar_sub_hover_bg_raw ? esc_attr( $this->colour( 'admbud_colours_adminbar_sub_hover_bg', $primary ) ) : esc_attr( $primary ); // admin bar flyout hover bg $p27 = $shadow_colour_raw ? esc_attr( $this->colour( 'admbud_colours_shadow_colour', '#000000' ) ) : ''; // drop shadow colour (empty = use default rgba) // Build box-shadow string. if ( $shadow_colour_raw ) { // If the value is already an rgba/rgb string, use it directly with the shadow offset. if ( strpos( $p27, 'rgb' ) === 0 ) { $box_shadow = "0 4px 12px {$p27}"; } elseif ( preg_match( '/^#?([0-9a-f]{6})$/i', $p27, $m ) ) { $r = hexdec( substr( $m[1], 0, 2 ) ); $g = hexdec( substr( $m[1], 2, 2 ) ); $b = hexdec( substr( $m[1], 4, 2 ) ); $box_shadow = "0 4px 12px rgba({$r},{$g},{$b},0.4)"; } else { $box_shadow = '0 4px 12px rgba(0,0,0,0.35)'; } } else { $box_shadow = '0 4px 12px rgba(0,0,0,0.35)'; } $p28 = $box_shadow; // drop shadow for flyout panels $p1_enc = str_replace( '#', '%23', $p1 ); // URL-encoded primary for inline SVG // Contrast colour for content that sits ON the primary (checkbox tick, // radio dot, etc.). The primary itself paints those controls' *background* // - primary-on-primary is invisible. Picked by luminance: near-white on // dark primaries, near-black on light ones. Plain hex for CSS use, URL- // encoded form for inline SVG `fill` attributes. $p1_text = esc_attr( \Admbud\Settings::contrast_text( $primary ) ); $p1_text_enc = str_replace( '#', '%23', $p1_text ); // Buffer the CSS string and emit via wp_add_inline_style instead of // direct