4, use white. * * @param string $hex Background colour (3- or 6-digit hex, with or without #) * @param string $light Return value for “white” (default '#ffffff') * @param string $dark Return value for “black” (default '#000000') * @return string */ function contrast_text_color(string $hex, string $light = '#ffffff', string $dark = '#000000'): string { // --- normalise ---------------------------------------------------- $hex = ltrim($hex, '#'); if (strlen($hex) === 3) { // #abc → #aabbcc $hex = preg_replace('/./', '$0$0', $hex); } [$r, $g, $b] = [ hexdec(substr($hex, 0, 2)) / 255, hexdec(substr($hex, 2, 2)) / 255, hexdec(substr($hex, 4, 2)) / 255, ]; // --- convert sRGB → linear RGB ----------------------------------- $linear = function (float $c): float { return $c <= 0.04045 ? $c / 12.92 : pow(($c + 0.055) / 1.055, 2.4); }; $R = $linear($r); $G = $linear($g); $B = $linear($b); // --- relative luminance (ITU-R BT.709) ---------------------------- $L_bg = 0.2126 * $R + 0.7152 * $G + 0.0722 * $B; // 0–1 // --- contrast ratios vs black (L=0) and white (L=1) --------------- $contrast_black = ($L_bg + 0.05) / 0.05; // bg lighter than black $contrast_white = 1.05 / ($L_bg + 0.05); // white vs bg // --- pick the winner --------------------------------------------- $useDark = $contrast_black >= $contrast_white; // override rule if dark is true but white "looks better" if ($useDark && $contrast_black < 5.5 && $contrast_white > 4) { $useDark = false; // switch to white } return $useDark ? $dark : $light; } } if (! function_exists('is_hex_color')) { function is_hex_color(mixed $value): bool { return is_string($value) && preg_match('/^#[0-9A-Fa-f]{6}$/', $value) === 1; } } if (! function_exists('calendar_color_palette')) { /** * returns a cleaned palette from config (valid hex only). * * @return array */ function calendar_color_palette(): array { $palette = config('kithkin.calendar.color_palette', []); if (! is_array($palette)) { $palette = []; } return array_values(array_filter($palette, fn ($c) => is_hex_color($c))); } } if (! function_exists('calendar_color_failsafe')) { function calendar_color_failsafe(): string { $fallback = config('kithkin.calendar.color_failsafe', '#1a1a1a'); return is_hex_color($fallback) ? $fallback : '#1a1a1a'; } } if (! function_exists('default_calendar_color')) { /** * returns the default calendar color from the configured palette. * if the palette is empty/invalid, falls back to the failsafe. */ function default_calendar_color(): string { $palette = calendar_color_palette(); if (count($palette) === 0) { return calendar_color_failsafe(); } $strategy = config('kithkin.calendar.default_color_strategy', 'first'); if ($strategy === 'random') { return $palette[array_rand($palette)]; } return $palette[0]; } } if (! function_exists('calendar_color')) { /** * pick a calendar color with a single source of truth for defaults. * * order: * 1) $data['color'] if valid * 2) $fallback if valid * 3) default_calendar_color() (palette-based) * 4) hard failsafe from config, then '#1a1a1a' * * @param array $data */ function calendar_color(array $data = [], ?string $fallback = null): string { $raw = $data['color'] ?? null; if (is_hex_color($raw)) { return $raw; } if (is_hex_color($fallback)) { return $fallback; } $default = default_calendar_color(); return is_hex_color($default) ? $default : calendar_color_failsafe(); } }