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