/** * Color utility functions shared across the project. * Extracted from BrandColorsContext to allow reuse and isolated testing. */ /** * Converts a hex color string to its RGB components. * @param hex - Hex color string (with or without leading `#`) * @returns Object with `r`, `g`, `b` values (0–255), or `null` if invalid * * @example * hexToRgb('#3b82f6') // { r: 59, g: 130, b: 246 } * hexToRgb('3b82f6') // { r: 59, g: 130, b: 246 } * hexToRgb('invalid') // null */ export function hexToRgb(hex: string): { r: number; g: number; b: number } | null { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16), } : null; } /** * Converts RGB components to an `rgba()` CSS string. * @param r - Red (0–255) * @param g - Green (0–255) * @param b - Blue (0–255) * @param alpha - Alpha (0–1), defaults to 1 * * @example * rgbToRgba(59, 130, 246, 0.5) // 'rgba(59, 130, 246, 0.5)' */ export function rgbToRgba(r: number, g: number, b: number, alpha = 1): string { return `rgba(${r}, ${g}, ${b}, ${alpha})`; } /** * Converts a hex color to an `rgba()` CSS string. * Returns `null` if the hex is invalid. * * @example * hexToRgba('#3b82f6', 0.15) // 'rgba(59, 130, 246, 0.15)' */ export function hexToRgba(hex: string, alpha = 1): string | null { const rgb = hexToRgb(hex); if (!rgb) return null; return rgbToRgba(rgb.r, rgb.g, rgb.b, alpha); } /** * Determines whether a hex color is considered "light" based on its luminance. * Uses the W3C relative luminance formula. * * @param hex - Hex color string * @returns `true` if the color is light, `false` if dark * * @example * isLightColor('#ffffff') // true * isLightColor('#000000') // false * isLightColor('#3b82f6') // false */ export function isLightColor(hex: string): boolean { const rgb = hexToRgb(hex); if (!rgb) return true; // W3C relative luminance const luminance = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255; return luminance > 0.5; }