// Helper function to convert hex to RGB export const hexToRgb = (hex: string): { r: number; g: number; b: number } => { // Remove the # if present hex = hex.replace("#", "") // Convert hex to RGB let r = 0, g = 0, b = 0 if (hex.length === 3) { r = Number.parseInt(hex[0] + hex[0], 16) g = Number.parseInt(hex[1] + hex[1], 16) b = Number.parseInt(hex[2] + hex[2], 16) } else if (hex.length === 6) { r = Number.parseInt(hex.substring(0, 2), 16) g = Number.parseInt(hex.substring(2, 4), 16) b = Number.parseInt(hex.substring(4, 6), 16) } return { r, g, b } } // Helper function to convert RGB to HSL export const rgbToHsl = (r: number, g: number, b: number): { h: number; s: number; l: number } => { r /= 255 g /= 255 b /= 255 const max = Math.max(r, g, b) const min = Math.min(r, g, b) let h = 0, s = 0, l = (max + min) / 2 if (max !== min) { const d = max - min s = l > 0.5 ? d / (2 - max - min) : d / (max + min) switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0) break case g: h = (b - r) / d + 2 break case b: h = (r - g) / d + 4 break } h /= 6 } return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100), } } // Add this function if it doesn't exist export function hexToHsl(hex: string): { h: number; s: number; l: number } { // Remove the # if present hex = hex.replace("#", "") // Convert hex to RGB let r = 0, g = 0, b = 0 if (hex.length === 3) { r = Number.parseInt(hex[0] + hex[0], 16) g = Number.parseInt(hex[1] + hex[1], 16) b = Number.parseInt(hex[2] + hex[2], 16) } else if (hex.length === 6) { r = Number.parseInt(hex.substring(0, 2), 16) g = Number.parseInt(hex.substring(2, 4), 16) b = Number.parseInt(hex.substring(4, 6), 16) } // Convert RGB to HSL r /= 255 g /= 255 b /= 255 const max = Math.max(r, g, b) const min = Math.min(r, g, b) let h = 0, s = 0, l = (max + min) / 2 if (max !== min) { const d = max - min s = l > 0.5 ? d / (2 - max - min) : d / (max + min) switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0) break case g: h = (b - r) / d + 2 break case b: h = (r - g) / d + 4 break } h /= 6 } return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100), } } // Calculate relative luminance using WCAG formula export const calculateLuminance = (r: number, g: number, b: number): number => { // Normalize RGB values to 0-1 const normalize = (value: number) => { const normalized = value / 255 return normalized <= 0.03928 ? normalized / 12.92 : Math.pow((normalized + 0.055) / 1.055, 2.4) } const rNorm = normalize(r) const gNorm = normalize(g) const bNorm = normalize(b) return 0.2126 * rNorm + 0.7152 * gNorm + 0.0722 * bNorm } // Calculate luminance from hex color export const hexToLuminance = (hex: string): number => { const { r, g, b } = hexToRgb(hex) return calculateLuminance(r, g, b) } // Convert luminance back to grayscale RGB values export const luminanceToRgb = (luminance: number): { r: number; g: number; b: number } => { // Clamp luminance between 0.05 and 0.95 to avoid pure black/white const clampedLuminance = Math.max(0.05, Math.min(0.95, luminance)) // Convert luminance to sRGB value const srgb = clampedLuminance <= 0.0031308 ? clampedLuminance * 12.92 : 1.055 * Math.pow(clampedLuminance, 1 / 2.4) - 0.055 const value = Math.round(srgb * 255) return { r: value, g: value, b: value } } // Convert RGB to hex export const rgbToHex = (r: number, g: number, b: number): string => { return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}` } // Main function to derive secondary text color from primary text color export const deriveSecondaryTextColor = (primaryTextColor: string, backgroundColor: string): string => { const primaryLuminance = hexToLuminance(primaryTextColor) const backgroundLuminance = hexToLuminance(backgroundColor) // Determine if background is light or dark const isLightBackground = backgroundLuminance > 0.5 // Calculate secondary luminance with 0.18 difference let secondaryLuminance: number if (isLightBackground) { // For light backgrounds, make secondary text lighter (higher luminance) secondaryLuminance = primaryLuminance + 0.18 } else { // For dark backgrounds, make secondary text darker (lower luminance) secondaryLuminance = primaryLuminance - 0.18 } // Convert back to RGB and then to hex const { r, g, b } = luminanceToRgb(secondaryLuminance) return rgbToHex(r, g, b) }