33 * Applies generated palettes to CSS variables for Plane's theme system
44 */
55
6- import { hexToOKLCH , oklchToCSS } from "./color-conversion" ;
6+ import { hexToOKLCH , oklchToCSS , getRelativeLuminance , getPerceptualBrightness } from "./color-conversion" ;
7+ import type { OKLCH } from "./color-conversion" ;
78import { ALPHA_MAPPING } from "./constants" ;
89import { generateThemePalettes } from "./palette-generator" ;
910import { getBrandMapping , getNeutralMapping , invertPalette } from "./theme-inversion" ;
1011
12+ /**
13+ * Color darkness detection methods
14+ */
15+ export type DarknessDetectionMethod = "wcag" | "oklch" | "perceptual" ;
16+
17+ /**
18+ * Determine if a color is dark using various methods
19+ *
20+ * Methods:
21+ * - 'wcag': Uses WCAG relative luminance (0-1 scale, threshold 0.5) - Most accurate for accessibility
22+ * - 'oklch': Uses OKLCH lightness (0-1 scale, threshold 0.5) - Good for perceptual uniformity
23+ * - 'perceptual': Uses weighted RGB brightness (0-255 scale, threshold 128) - Simple and fast
24+ *
25+ * @param brandColor - Brand color in hex format
26+ * @param method - Detection method to use (default: 'wcag')
27+ * @returns true if the color is dark, false if light
28+ */
29+ export function isColorDark ( brandColor : string , method : DarknessDetectionMethod = "wcag" ) : boolean {
30+ switch ( method ) {
31+ case "wcag" : {
32+ // WCAG relative luminance: 0 (black) to 1 (white)
33+ // Threshold of 0.5 means colors darker than 50% gray are considered dark
34+ const luminance = getRelativeLuminance ( brandColor ) ;
35+ return luminance < 0.5 ;
36+ }
37+ case "oklch" : {
38+ // OKLCH lightness: 0 (black) to 1 (white)
39+ // Threshold of 0.5 provides good balance for most colors
40+ const oklch = hexToOKLCH ( brandColor ) ;
41+ return oklch . l < 0.5 ;
42+ }
43+ case "perceptual" : {
44+ // Perceptual brightness: 0 (black) to 255 (white)
45+ // Threshold of 128 is the midpoint
46+ const brightness = getPerceptualBrightness ( brandColor ) ;
47+ return brightness < 128 ;
48+ }
49+ default :
50+ return getRelativeLuminance ( brandColor ) < 0.5 ;
51+ }
52+ }
53+
54+ /**
55+ * Get contrasting text colors for use on a colored background
56+ * Returns white text for dark backgrounds, black text for light backgrounds
57+ *
58+ * @param brandColor - Brand color in hex format
59+ * @param method - Detection method to use (default: 'wcag')
60+ * @returns Object with text and icon colors in OKLCH format
61+ */
62+ export function getOnColorTextColors (
63+ brandColor : string ,
64+ method : DarknessDetectionMethod = "wcag"
65+ ) : {
66+ textColor : OKLCH ;
67+ iconColor : OKLCH ;
68+ } {
69+ const isDark = isColorDark ( brandColor , method ) ;
70+ const white : OKLCH = { l : 1 , c : 0 , h : 0 } ;
71+ const black : OKLCH = { l : 0 , c : 0 , h : 0 } ;
72+
73+ return {
74+ textColor : isDark ? white : black ,
75+ iconColor : isDark ? black : white ,
76+ } ;
77+ }
78+
1179/**
1280 * Apply custom theme using 2-color palette system
1381 * Generates full palettes from brand and neutral colors
@@ -32,7 +100,6 @@ export function applyCustomTheme(brandColor: string, neutralColor: string, mode:
32100 // Generate palettes directly in OKLCH color space
33101 const { brandPalette, neutralPalette } = generateThemePalettes ( brandColor , neutralColor , mode ) ;
34102 const neutralOKLCH = hexToOKLCH ( neutralColor ) ;
35- const brandOKLCH = hexToOKLCH ( brandColor ) ;
36103
37104 // For dark mode, invert the palettes
38105 const activeBrandPalette = mode === "dark" ? invertPalette ( brandPalette ) : brandPalette ;
@@ -57,14 +124,11 @@ export function applyCustomTheme(brandColor: string, neutralColor: string, mode:
57124 themeElement . style . setProperty ( `--color-alpha-black-${ key } ` , oklchToCSS ( neutralOKLCH , value * 100 ) ) ;
58125 } ) ;
59126
60- const isBrandColorDark = brandOKLCH . l < 0.2 ;
61- const whiteInOKLCH = { l : 1 , c : 0 , h : 0 } ;
62- const blackInOKLCH = { l : 0 , c : 0 , h : 0 } ;
63- themeElement . style . setProperty ( `--text-color-on-color` , oklchToCSS ( isBrandColorDark ? whiteInOKLCH : blackInOKLCH ) ) ;
64- themeElement . style . setProperty (
65- `--text-color-icon-on-color` ,
66- oklchToCSS ( isBrandColorDark ? blackInOKLCH : whiteInOKLCH )
67- ) ;
127+ // Apply contrasting text colors for use on colored backgrounds
128+ // Uses WCAG relative luminance for accurate contrast determination
129+ const { textColor, iconColor } = getOnColorTextColors ( brandColor , "wcag" ) ;
130+ themeElement . style . setProperty ( `--text-color-on-color` , oklchToCSS ( textColor ) ) ;
131+ themeElement . style . setProperty ( `--text-color-icon-on-color` , oklchToCSS ( iconColor ) ) ;
68132}
69133
70134/**
0 commit comments