diff --git a/app/client/packages/design-system/theming/src/color/ColorsAccessor.ts b/app/client/packages/design-system/theming/src/color/ColorsAccessor.ts index 34cfbe815f..3296823435 100644 --- a/app/client/packages/design-system/theming/src/color/ColorsAccessor.ts +++ b/app/client/packages/design-system/theming/src/color/ColorsAccessor.ts @@ -11,6 +11,18 @@ export class ColorsAccessor { return this; } + get lightness() { + return this.color.oklch.l; + } + + get chroma() { + return this.color.oklch.c; + } + + get hue() { + return this.color.oklch.h; + } + /* Lightness */ get isVeryDark() { return this.color.oklch.l < 0.3; @@ -25,19 +37,11 @@ export class ColorsAccessor { return this.color.oklch.c < 0.04; } - get isColorful() { - return this.color.oklch.c > 0.136; - } - /* Hue */ get isCold() { return this.color.oklch.h >= 120 && this.color.oklch.h <= 300; } - get isBlue() { - return this.color.oklch.h >= 230 && this.color.oklch.h <= 270; - } - get isGreen() { return this.color.oklch.h >= 116 && this.color.oklch.h <= 165; } @@ -49,16 +53,4 @@ export class ColorsAccessor { get isRed() { return this.color.oklch.h >= 5 && this.color.oklch.h <= 49; } - - get lightness() { - return this.color.oklch.l; - } - - get chroma() { - return this.color.oklch.c; - } - - get hue() { - return this.color.oklch.h; - } } diff --git a/app/client/packages/design-system/theming/src/color/DarkModeTheme.ts b/app/client/packages/design-system/theming/src/color/DarkModeTheme.ts index c6516b666c..723fa5c1d5 100644 --- a/app/client/packages/design-system/theming/src/color/DarkModeTheme.ts +++ b/app/client/packages/design-system/theming/src/color/DarkModeTheme.ts @@ -43,7 +43,6 @@ export class DarkModeTheme implements ColorModeTheme { public getColors = () => { return { - // bg bg: this.bg.toString(), bgAccent: this.bgAccent.toString(), bgAccentHover: this.bgAccentHover.toString(), @@ -51,6 +50,11 @@ export class DarkModeTheme implements ColorModeTheme { bgAccentSubtleHover: this.bgAccentSubtleHover.toString(), bgAccentSubtleActive: this.bgAccentSubtleActive.toString(), bgAssistive: this.bgAssistive.toString(), + bgNeutral: this.bgNeutral.toString(), + bgNeutralHover: this.bgNeutralHover.toString(), + bgNeutralActive: this.bgNeutralActive.toString(), + bgNeutralSubtleHover: this.bgNeutralSubtleHover.toString(), + bgNeutralSubtleActive: this.bgNeutralSubtleActive.toString(), bgPositive: this.bgPositive.to("sRGB").toString(), bgPositiveHover: this.bgPositiveHover.to("sRGB").toString(), bgPositiveActive: this.bgPositiveActive.to("sRGB").toString(), @@ -66,40 +70,36 @@ export class DarkModeTheme implements ColorModeTheme { bgWarningActive: this.bgWarningActive.to("sRGB").toString(), bgWarningSubtleHover: this.bgWarningSubtleHover.to("sRGB").toString(), bgWarningSubtleActive: this.bgWarningSubtleActive.to("sRGB").toString(), - bgNeutral: this.bgNeutral.toString(), - bgNeutralHover: this.bgNeutralHover.toString(), - bgNeutralActive: this.bgNeutralActive.toString(), - bgNeutralSubtle: this.bgNeutralSubtle.toString(), - bgNeutralSubtleHover: this.bgNeutralSubtleHover.toString(), - bgNeutralSubtleActive: this.bgNeutralSubtleActive.toString(), - // fg + fg: this.fg.toString(), fgAccent: this.fgAccent.toString(), - fgOnAccent: this.fgOnAccent.toString(), - fgPositive: this.fgPositive.to("sRGB").toString(), - fgOnPositive: this.fgOnPositive.to("sRGB").toString(), - fgNegative: this.fgNegative.to("sRGB").toString(), - fgOnNegative: this.fgOnNegative.to("sRGB").toString(), fgNeutral: this.fgNeutral.toString(), - fgOnNeutral: this.fgOnNeutral.to("sRGB").toString(), + fgPositive: this.fgPositive.to("sRGB").toString(), + fgNegative: this.fgNegative.to("sRGB").toString(), fgWarning: this.fgWarning.to("sRGB").toString(), - fgOnWarning: this.fgOnWarning.to("sRGB").toString(), + + fgOnAccent: this.fgOnAccent.toString(), fgOnAssistive: this.fgOnAssistive.to("sRGB").toString(), - // bd + fgOnNeutral: this.fgOnNeutral.to("sRGB").toString(), + fgOnPositive: this.fgOnPositive.to("sRGB").toString(), + fgOnNegative: this.fgOnNegative.to("sRGB").toString(), + fgOnWarning: this.fgOnWarning.to("sRGB").toString(), + bdAccent: this.bdAccent.toString(), - bdOnAccent: this.bdOnAccent.toString(), bdFocus: this.bdFocus.toString(), - bdNegative: this.bdNegative.to("sRGB").toString(), - bdNegativeHover: this.bdNegativeHover.to("sRGB").toString(), - bdOnNegative: this.bdOnNegative.to("sRGB").toString(), bdNeutral: this.bdNeutral.toString(), bdNeutralHover: this.bdNeutralHover.toString(), - bdOnNeutral: this.bdOnNeutral.to("sRGB").toString(), bdPositive: this.bdPositive.to("sRGB").toString(), bdPositiveHover: this.bdPositiveHover.to("sRGB").toString(), - bdOnPositive: this.bdOnPositive.to("sRGB").toString(), + bdNegative: this.bdNegative.to("sRGB").toString(), + bdNegativeHover: this.bdNegativeHover.to("sRGB").toString(), bdWarning: this.bdWarning.to("sRGB").toString(), bdWarningHover: this.bdWarningHover.to("sRGB").toString(), + + bdOnAccent: this.bdOnAccent.toString(), + bdOnNeutral: this.bdOnNeutral.to("sRGB").toString(), + bdOnPositive: this.bdOnPositive.to("sRGB").toString(), + bdOnNegative: this.bdOnNegative.to("sRGB").toString(), bdOnWarning: this.bdOnWarning.to("sRGB").toString(), }; }; @@ -108,52 +108,53 @@ export class DarkModeTheme implements ColorModeTheme { * Background colors */ - // Main application background color. - // Applies to canvas. In dark mode it is extremely dark (and therefore desatured) shade of user-set seed color. - // This ensures harmonious combination with main accents and neutrals. private get bg() { + // Main application background color. + // Applies to canvas. In dark mode it is extremely dark (and therefore desatured) shade of user-set seed color. + // This ensures harmonious combination with main accents and neutrals. const color = this.seedColor.clone(); + color.oklch.l = 0.15; + // If initial seed had non-substantial amount of chroma, make sure bg is achromatic. if (this.seedIsAchromatic) { - color.oklch.l = 0.15; color.oklch.c = 0; - return color; } - color.oklch.l = 0.15; - color.oklch.c = 0.064; + if (this.seedIsAchromatic) { + color.oklch.c = 0.064; + } + return color; } - // Main accent color. Largely is the same as user-set seed color. private get bgAccent() { + // Main accent color. Largely is the same as user-set seed color. const color = this.seedColor.clone(); // If the seed is very dark, set it to minimal lightness to make sure it's visible against bg. if (this.seedIsVeryDark) { color.oklch.l = 0.3; - return color; } return color; } - // Hover state. Slightly lighter than the resting state to produce the effect of moving closer to the viewer / inspection. private get bgAccentHover() { + // Hover state. Slightly lighter than the resting state to produce the effect of moving closer to the viewer / inspection. const color = this.bgAccent.clone(); // “Slightly lighter” is very dependent on the initial amount of lightness as well as how light (or dark) the surroundings are. if (this.seedLightness < 0.3) { - color.oklch.l = this.bgAccent.oklch.l + 0.05; + color.oklch.l += 0.05; } if (this.seedLightness >= 0.3 && this.seedLightness < 0.45) { - color.oklch.l = this.bgAccent.oklch.l + 0.04; + color.oklch.l += 0.04; } if (this.seedLightness >= 0.45 && this.seedLightness < 0.77) { - color.oklch.l = this.bgAccent.oklch.l + 0.03; + color.oklch.l += 0.03; } // In this top range lightness increase is supplemented by chroma increase to make the hover effect more perceptibe. @@ -163,8 +164,8 @@ export class DarkModeTheme implements ColorModeTheme { !this.seedIsAchromatic && this.seedIsCold ) { - color.oklch.l = this.bgAccent.oklch.l + 0.04; - color.oklch.c = this.bgAccent.oklch.c + 0.05; + color.oklch.l += 0.04; + color.oklch.c += 0.05; } // Warm colors require a little bit more lightness in this range than colds to be sufficiently perceptually lighter. @@ -174,8 +175,8 @@ export class DarkModeTheme implements ColorModeTheme { !this.seedIsAchromatic && !this.seedIsCold ) { - color.oklch.l = this.bgAccent.oklch.l + 0.06; - color.oklch.c = this.bgAccent.oklch.c + 0.1; + color.oklch.l += 0.06; + color.oklch.c += 0.1; } if ( @@ -183,43 +184,43 @@ export class DarkModeTheme implements ColorModeTheme { this.seedLightness < 0.85 && this.seedIsAchromatic ) { - color.oklch.l = this.bgAccent.oklch.l + 0.04; + color.oklch.l += 0.04; } // For very light seeds it's impossible to produce hover state that is sufficiently perceptibly lighter, switching to darker hovers. if (this.seedLightness >= 0.85) { - color.oklch.l = this.bgAccent.oklch.l - 0.07; + color.oklch.l -= 0.07; } return color; } - // “Pressed” state. Slightly darker than the resting state to produce the effect of moving further from the viewer / being pushed down. private get bgAccentActive() { + // “Pressed” state. Slightly darker than the resting state to produce the effect of moving further from the viewer / being pushed down. const color = this.bgAccent.clone(); // “Slightly darker” is very dependent on the initial amount of lightness as well as how light (or dark) the surroundings are. if (this.seedLightness < 0.4) { - color.oklch.l = this.bgAccent.oklch.l - 0.02; + color.oklch.l -= 0.02; } if (this.seedLightness >= 0.4 && this.seedLightness < 0.7) { - color.oklch.l = this.bgAccent.oklch.l - 0.04; + color.oklch.l -= 0.04; } if (this.seedLightness >= 0.7 && this.seedLightness < 0.85) { - color.oklch.l = this.bgAccent.oklch.l - 0.05; + color.oklch.l -= 0.05; } if (this.seedLightness >= 0.85) { - color.oklch.l = this.bgAccent.oklch.l - 0.13; + color.oklch.l -= 0.13; } return color; } - // Subtle variant of bgAccent. Darker and less saturated. private get bgAccentSubtle() { + // Subtle variant of bgAccent. Darker and less saturated. const color = this.seedColor.clone(); if (this.seedLightness > 0.25) { @@ -245,7 +246,7 @@ export class DarkModeTheme implements ColorModeTheme { private get bgAccentSubtleHover() { const color = this.bgAccentSubtle.clone(); - color.oklch.l = color.oklch.l + 0.03; + color.oklch.l += 0.03; return color; } @@ -253,198 +254,32 @@ export class DarkModeTheme implements ColorModeTheme { private get bgAccentSubtleActive() { const color = this.bgAccentSubtle.clone(); - color.oklch.l = color.oklch.l - 0.02; + color.oklch.l -= 0.02; return color; } - // Positive background, green. - private get bgPositive() { - const color = new Color("oklch", [0.62, 0.17, 145]); + private get bgAssistive() { + const color = this.seedColor.clone(); - // If the seed color is also green, adjust positive by hue to make it distinct from accent. - if (this.seedIsGreen && this.seedColor.oklch.c > 0.09) { - if (this.seedColor.oklch.h < 145) { - color.oklch.h = 155; - } - if (this.seedColor.oklch.h >= 145) { - color.oklch.h = 135; - } + // Background color for assistive UI elements (e.g. tooltip); light to stand out against bg + color.oklch.l = 0.94; + color.oklch.c = 0.03; + + if (this.seedIsAchromatic) { + color.oklch.c = 0; } return color; } - private get bgPositiveHover() { - const color = this.bgPositive.clone(); - - // Lightness of bgPositive is known, no additional checks like in bgAccentHover - color.oklch.l = color.oklch.l + 0.03; - - return color; - } - - private get bgPositiveActive() { - const color = this.bgPositive.clone(); - - // Lightness of bgPositive is known, no additional checks like in bgAccentActive - color.oklch.l = color.oklch.l - 0.04; - - return color; - } - - private get bgPositiveSubtle() { - const color = this.bgPositive.clone(); - - color.oklch.l = 0.25; - color.oklch.c = 0.06; - - return color; - } - - private get bgPositiveSubtleHover() { - const color = this.bgPositiveSubtle.clone(); - - color.oklch.l = color.oklch.l + 0.03; - - return color; - } - - private get bgPositiveSubtleActive() { - const color = this.bgPositiveSubtle.clone(); - - color.oklch.l = color.oklch.l - 0.02; - - return color; - } - - // Warning background, yellow - private get bgWarning() { - const color = new Color("oklch", [0.75, 0.15, 85]); - - // Check for clashes with seed, adjust by hue to make it distinct - if (this.seedIsYellow && this.seedColor.oklch.c > 0.09) { - if (this.seedColor.oklch.h < 85) { - color.oklch.h = 95; - } - if (this.seedColor.oklch.h >= 85) { - color.oklch.h = 70; - } - } - - return color; - } - - private get bgWarningHover() { - const color = this.bgWarning.clone(); - - // Lightness of bgWarning is known, no additional checks like in bgAccentHover - color.oklch.l = color.oklch.l + 0.04; - - return color; - } - - private get bgWarningActive() { - const color = this.bgWarning.clone(); - - // Lightness of bgWarning is known, no additional checks like in bgAccentActive - color.oklch.l = color.oklch.l - 0.05; - - return color; - } - - private get bgWarningSubtle() { - const color = this.bgWarning.clone(); - - color.oklch.l = 0.25; - color.oklch.c = 0.04; - - return color; - } - - private get bgWarningSubtleHover() { - const color = this.bgWarningSubtle.clone(); - - color.oklch.l = color.oklch.l + 0.03; - - return color; - } - - private get bgWarningSubtleActive() { - const color = this.bgWarningSubtle.clone(); - - color.oklch.l = color.oklch.l - 0.02; - - return color; - } - - // Negative background, red. - private get bgNegative() { - const color = new Color("oklch", [0.55, 0.22, 27]); - - // If seed is red adjust negative by hue to make it distinct - if (this.seedIsRed && this.seedColor.oklch.c > 0.07) { - if (this.seedColor.oklch.h < 27) { - color.oklch.h = 32; - } - if (this.seedColor.oklch.h >= 27) { - color.oklch.h = 22; - } - } - - return color; - } - - private get bgNegativeHover() { - const color = this.bgNegative.clone(); - - // Lightness of bgNegative is known, no additional checks like in bgAccentHover - color.oklch.l = color.oklch.l + 0.03; - - return color; - } - - private get bgNegativeActive() { - const color = this.bgNegative.clone(); - - // Lightness of bgNegative is known, no additional checks like in bgAccentActive - color.oklch.l = color.oklch.l - 0.04; - - return color; - } - - private get bgNegativeSubtle() { - const color = this.bgNegative.clone(); - - color.oklch.l = 0.25; - color.oklch.c = 0.09; - - return color; - } - - private get bgNegativeSubtleHover() { - const color = this.bgNegativeSubtle.clone(); - - color.oklch.l = color.oklch.l + 0.03; - - return color; - } - - private get bgNegativeSubtleActive() { - const color = this.bgNegativeSubtle.clone(); - - color.oklch.l = color.oklch.l - 0.02; - - return color; - } - - // Low chroma, but not 0, if possible, to produce harmony with accents in the UI private get bgNeutral() { + // Low chroma, but not 0, if possible, to produce harmony with accents in the UI const color = this.bgAccent.clone(); // For darker accents it helps to increase neutral's lightness a little, so it's visible against bg if (this.bgAccent.oklch.l < 0.5) { - color.oklch.l = color.oklch.l + 0.05; + color.oklch.l += 0.05; } if (this.seedIsAchromatic) { @@ -466,21 +301,20 @@ export class DarkModeTheme implements ColorModeTheme { const color = this.bgNeutral.clone(); // Simplified and adjusted version of bgAccentHover algorithm (bgNeutral has very low or no chroma) - if (this.bgNeutral.oklch.l >= 0.85) { - color.oklch.l = color.oklch.l - 0.07; + color.oklch.l -= 0.07; } if (this.bgNeutral.oklch.l >= 0.77 && this.bgNeutral.oklch.l < 0.85) { - color.oklch.l = color.oklch.l + 0.04; + color.oklch.l += 0.04; } if (this.bgNeutral.oklch.l >= 0.45 && this.bgNeutral.oklch.l < 0.77) { - color.oklch.l = color.oklch.l + 0.03; + color.oklch.l += 0.03; } if (this.bgNeutral.oklch.l >= 0.3 && this.bgNeutral.oklch.l < 0.45) { - color.oklch.l = color.oklch.l + 0.04; + color.oklch.l += 0.04; } return color; @@ -491,19 +325,19 @@ export class DarkModeTheme implements ColorModeTheme { // Simplified and adjusted version of bgAccentHover algorithm (bgNeutral has very low or no chroma) if (this.bgNeutral.oklch.l < 0.4) { - color.oklch.l = this.bgAccent.oklch.l - 0.01; + color.oklch.l -= 0.01; } if (this.bgNeutral.oklch.l >= 0.4 && this.bgNeutral.oklch.l < 0.7) { - color.oklch.l = this.bgAccent.oklch.l - 0.04; + color.oklch.l -= 0.04; } if (this.bgNeutral.oklch.l >= 0.7 && this.bgNeutral.oklch.l < 0.85) { - color.oklch.l = this.bgAccent.oklch.l - 0.05; + color.oklch.l -= 0.05; } if (this.bgNeutral.oklch.l >= 0.85) { - color.oklch.l = this.bgAccent.oklch.l - 0.13; + color.oklch.l -= 0.13; } return color; @@ -513,7 +347,6 @@ export class DarkModeTheme implements ColorModeTheme { const color = this.seedColor.clone(); // Adjusted version of bgAccentSubtle (less or no chroma) - if (this.seedLightness > 0.25) { color.oklch.l = 0.25; } @@ -537,7 +370,7 @@ export class DarkModeTheme implements ColorModeTheme { private get bgNeutralSubtleHover() { const color = this.bgNeutralSubtle.clone(); - color.oklch.l = color.oklch.l + 0.03; + color.oklch.l += 0.03; return color; } @@ -545,93 +378,242 @@ export class DarkModeTheme implements ColorModeTheme { private get bgNeutralSubtleActive() { const color = this.bgNeutralSubtle.clone(); - color.oklch.l = color.oklch.l - 0.02; + color.oklch.l -= 0.02; return color; } - private get bgAssistive() { - const color = this.seedColor.clone(); + private get bgPositive() { + // Positive background, green. + const color = new Color("oklch", [0.62, 0.17, 145]); - // Background color for assistive UI elements (e.g. tooltip); light to stand out against bg - color.oklch.l = 0.94; - color.oklch.c = 0.03; - - if (this.seedIsAchromatic) { - color.oklch.c = 0; + // If the seed color is also green, adjust positive by hue to make it distinct from accent. + if (this.seedIsGreen && this.seedChroma > 0.09) { + if (this.seedHue < 145) { + color.oklch.h = 155; + } + if (this.seedHue >= 145) { + color.oklch.h = 135; + } } return color; } + private get bgPositiveHover() { + const color = this.bgPositive.clone(); + + // Lightness of bgPositive is known, no additional checks like in bgAccentHover + color.oklch.l += 0.03; + + return color; + } + + private get bgPositiveActive() { + const color = this.bgPositive.clone(); + + // Lightness of bgPositive is known, no additional checks like in bgAccentActive + color.oklch.l -= 0.04; + + return color; + } + + private get bgPositiveSubtle() { + const color = this.bgPositive.clone(); + + color.oklch.l = 0.25; + color.oklch.c = 0.06; + + return color; + } + + private get bgPositiveSubtleHover() { + const color = this.bgPositiveSubtle.clone(); + + color.oklch.l += 0.03; + + return color; + } + + private get bgPositiveSubtleActive() { + const color = this.bgPositiveSubtle.clone(); + + color.oklch.l -= 0.02; + + return color; + } + + private get bgNegative() { + // Negative background, red. + const color = new Color("oklch", [0.55, 0.22, 27]); + + // If seed is red adjust negative by hue to make it distinct + if (this.seedIsRed && this.seedChroma > 0.07) { + if (this.seedHue < 27) { + color.oklch.h = 32; + } + if (this.seedHue >= 27) { + color.oklch.h = 22; + } + } + + return color; + } + + private get bgNegativeHover() { + const color = this.bgNegative.clone(); + + // Lightness of bgNegative is known, no additional checks like in bgAccentHover + color.oklch.l += 0.03; + + return color; + } + + private get bgNegativeActive() { + const color = this.bgNegative.clone(); + + // Lightness of bgNegative is known, no additional checks like in bgAccentActive + color.oklch.l -= 0.04; + + return color; + } + + private get bgNegativeSubtle() { + const color = this.bgNegative.clone(); + + color.oklch.l = 0.25; + color.oklch.c = 0.09; + + return color; + } + + private get bgNegativeSubtleHover() { + const color = this.bgNegativeSubtle.clone(); + + color.oklch.l += 0.03; + + return color; + } + + private get bgNegativeSubtleActive() { + const color = this.bgNegativeSubtle.clone(); + + color.oklch.l -= 0.02; + + return color; + } + + private get bgWarning() { + // Warning background, yellow + const color = new Color("oklch", [0.75, 0.15, 85]); + + // Check for clashes with seed, adjust by hue to make it distinct + if (this.seedIsYellow && this.seedChroma > 0.09) { + if (this.seedHue < 85) { + color.oklch.h = 95; + } + if (this.seedHue >= 85) { + color.oklch.h = 70; + } + } + + return color; + } + + private get bgWarningHover() { + const color = this.bgWarning.clone(); + + // Lightness of bgWarning is known, no additional checks like in bgAccentHover + color.oklch.l += 0.04; + + return color; + } + + private get bgWarningActive() { + const color = this.bgWarning.clone(); + + // Lightness of bgWarning is known, no additional checks like in bgAccentActive + color.oklch.l -= 0.05; + + return color; + } + + private get bgWarningSubtle() { + const color = this.bgWarning.clone(); + + color.oklch.l = 0.25; + color.oklch.c = 0.04; + + return color; + } + + private get bgWarningSubtleHover() { + const color = this.bgWarningSubtle.clone(); + + color.oklch.l += 0.03; + + return color; + } + + private get bgWarningSubtleActive() { + const color = this.bgWarningSubtle.clone(); + + color.oklch.l -= 0.02; + + return color; + } + /* * Foreground colors */ - // Main application foreground color. - // Applies to static text and similar. In dark mode it is light (and therefore desatured) tint of user-set seed color. - // This ensures harmonious combination with main accents and neutrals. + private get fg() { + // Main application foreground color. + // Applies to static text and similar. In dark mode it is light (and therefore desatured) tint of user-set seed color. + // This ensures harmonious combination with main accents and neutrals. const color = this.seedColor.clone(); + color.oklch.l = 0.965; + // If seed color didn't have substantial amount of chroma make sure fg is achromatic. if (this.seedIsAchromatic) { - color.oklch.l = 0.965; color.oklch.c = 0; - return color; } - color.oklch.l = 0.965; - color.oklch.c = 0.024; + if (this.seedIsAchromatic) { + color.oklch.c = 0.024; + } + return color; } - // Accent foreground/content color. private get fgAccent() { + // Accent foreground/content color. const color = this.seedColor.clone(); // For light content on dark background APCA contrast is negative. −60 is “The minimum level recommended for content text that is not body, column, or block text. In other words, text you want people to read.” Failure to reach this contrast level is most likely due to low lightness. Lightness and chroma are set to ones that reach the threshold universally regardless of hue. if (this.bg.contrastAPCA(this.seedColor) >= -60) { + color.oklch.l = 0.79; + if (this.seedIsAchromatic) { - color.oklch.l = 0.79; color.oklch.c = 0; - return color; } - color.oklch.l = 0.79; - color.oklch.c = 0.136; - return color; - } - return color; - } - - // Negative foreground is produced from the initially adjusted background color (see above). Additional tweaks are applied to make sure it's distinct from fgAccent when seed is red. - private get fgNegative() { - const color = this.bgNegative.clone(); - color.oklch.l = color.oklch.l + 0.05; - color.oklch.c = color.oklch.c + 0.1; - color.oklch.h = color.oklch.h - 10; - - if ( - this.seedIsRed && - !this.seedIsAchromatic && - this.fgAccent.oklch.l < 0.5 && - this.fgAccent.oklch.h < 27 - ) { - color.oklch.l = color.oklch.l + 0.05; - color.oklch.c = color.oklch.c + 0.05; - color.oklch.h = color.oklch.h - 15; + if (this.seedIsAchromatic) { + color.oklch.c = 0.136; + } } return color; } - // Desatured version of the seed for harmonious combination with backgrounds and accents. private get fgNeutral() { + // Desatured version of the seed for harmonious combination with backgrounds and accents. const color = this.fgAccent.clone(); // Minimal contrast that we set for fgAccent (60) is too low for a gray color if (this.bg.contrastAPCA(this.fgAccent) < 75) { - color.oklch.l = color.oklch.l + 0.04; + color.oklch.l += 0.04; } if (this.seedIsAchromatic) { @@ -649,8 +631,59 @@ export class DarkModeTheme implements ColorModeTheme { return color; } - // Foreground for content on top of bgAccent + private get fgPositive() { + // Positive foreground is produced from the initially adjusted background color (see above). Additional tweaks are applied to make sure it's distinct from fgAccent when seed is green. + const color = this.bgPositive.clone(); + + color.oklch.l += 0.08; + + if ( + this.seedIsGreen && + !this.seedIsAchromatic && + this.fgAccent.oklch.l > 0.5 && + this.fgAccent.oklch.h < 145 + ) { + color.oklch.h -= 5; + } + + return color; + } + + private get fgNegative() { + // Negative foreground is produced from the initially adjusted background color (see above). Additional tweaks are applied to make sure it's distinct from fgAccent when seed is red. + const color = this.bgNegative.clone(); + color.oklch.l += 0.05; + color.oklch.c += 0.1; + color.oklch.h -= 10; + + if ( + this.seedIsRed && + !this.seedIsAchromatic && + this.fgAccent.oklch.l < 0.5 && + this.fgAccent.oklch.h < 27 + ) { + color.oklch.l += 0.05; + color.oklch.c += 0.05; + color.oklch.h -= 15; + } + + return color; + } + + private get fgWarning() { + // Warning foreground is produced from the initially adjusted background color (see above). + const color = this.bgWarning.clone(); + + // Yellow hue interval in OKLCh is less symmetrical than green, compensation is applied to results of bgNegative + color.oklch.l += 0.12; + color.oklch.c += 0.1; + color.oklch.h -= 9; + + return color; + } + private get fgOnAccent() { + // Foreground for content on top of bgAccent const tint = this.seedColor.clone(); const shade = this.seedColor.clone(); @@ -680,36 +713,6 @@ export class DarkModeTheme implements ColorModeTheme { return shade; } - // Positive foreground is produced from the initially adjusted background color (see above). Additional tweaks are applied to make sure it's distinct from fgAccent when seed is green. - private get fgPositive() { - const color = this.bgPositive.clone(); - - color.oklch.l = color.oklch.l + 0.08; - - if ( - this.seedIsGreen && - !this.seedIsAchromatic && - this.fgAccent.oklch.l > 0.5 && - this.fgAccent.oklch.h < 145 - ) { - color.oklch.h = color.oklch.h - 5; - } - - return color; - } - - // Warning foreground is produced from the initially adjusted background color (see above). - private get fgWarning() { - const color = this.bgWarning.clone(); - - // Yellow hue interval in OKLCh is less symmetrical than green, compensation is applied to results of bgNegative - color.oklch.l = color.oklch.l + 0.12; - color.oklch.c = color.oklch.c + 0.1; - color.oklch.h = color.oklch.h - 9; - - return color; - } - private get fgOnNeutral() { // Simplified and adjusted version of fgOnAccent const tint = this.bgNeutral.clone(); @@ -748,25 +751,6 @@ export class DarkModeTheme implements ColorModeTheme { return tint; } - private get fgOnWarning() { - // Simplified and adjusted version of fgOnAccent - const tint = this.bgWarning.clone(); - const shade = this.bgWarning.clone(); - - // Light and dark derivatives of the bgWarning - tint.oklch.l = 0.94; - shade.oklch.l = 0.27; - - // Check which of them has better contrast with bgAccent - if ( - -this.bgWarning.contrastAPCA(tint) < this.bgWarning.contrastAPCA(shade) - ) { - return shade; - } - - return tint; - } - private get fgOnNegative() { // Simplified and adjusted version of fgOnAccent const tint = this.bgNegative.clone(); @@ -786,9 +770,29 @@ export class DarkModeTheme implements ColorModeTheme { return tint; } + private get fgOnWarning() { + // Simplified and adjusted version of fgOnAccent + const tint = this.bgWarning.clone(); + const shade = this.bgWarning.clone(); + + // Light and dark derivatives of the bgWarning + tint.oklch.l = 0.94; + shade.oklch.l = 0.27; + + // Check which of them has better contrast with bgAccent + if ( + -this.bgWarning.contrastAPCA(tint) < this.bgWarning.contrastAPCA(shade) + ) { + return shade; + } + + return tint; + } + /* * Border colors */ + private get bdAccent() { const color = this.seedColor.clone(); @@ -797,31 +801,12 @@ export class DarkModeTheme implements ColorModeTheme { if (this.seedIsAchromatic) { color.oklch.l = 0.82; color.oklch.c = 0; - return color; } - color.oklch.l = 0.75; - color.oklch.c = 0.15; - return color; - } - - return color; - } - - private get bdOnAccent() { - // Separator on bgAccent, low contrast to not pull attention from actual separated content elements - const color = this.bgAccent.clone(); - - if (this.bgAccent.oklch.l >= 0.7) { - color.oklch.l = this.bgAccent.oklch.l - 0.22; - } - - if (this.bgAccent.oklch.l < 0.7 && this.bgAccent.oklch.l >= 0.4) { - color.oklch.l = this.bgAccent.oklch.l - 0.29; - } - - if (this.bgAccent.oklch.l < 0.4) { - color.oklch.l = this.bgAccent.oklch.l - 0.36; + if (!this.seedIsAchromatic) { + color.oklch.l = 0.75; + color.oklch.c = 0.15; + } } return color; @@ -845,68 +830,18 @@ export class DarkModeTheme implements ColorModeTheme { } // Green-red color blindness is among the most prevalent, so instead of 180 we're rotating hue by additional 60° - color.oklch.h = this.seedHue - 240; + color.oklch.h -= 240; // Additional adjustments for red, pinks, magentas if ((this.seedHue >= 0 && this.seedHue <= 55) || this.seedHue >= 340) { - color.oklch.h = color.oklch.h + 160; + color.oklch.h += 160; } return color; } - // Negative (red) border. Produced out of bgNegative. Additional compensations are applied if seed is within red range. - private get bdNegative() { - const color = this.bgNegative.clone(); - - if ( - this.bdAccent.oklch.l > 0.5 && - this.bdAccent.oklch.c > 0.15 && - this.bdAccent.oklch.h < 27 && - this.bdAccent.oklch.h >= 5 - ) { - color.oklch.l = color.oklch.l + 0.18; - } - - if ( - this.bdAccent.oklch.l > 0.5 && - this.bdAccent.oklch.c > 0.15 && - this.bdAccent.oklch.h >= 27 && - this.bdAccent.oklch.h < 50 - ) { - color.oklch.h = color.oklch.h - 5; - color.oklch.l = color.oklch.l + 0.05; - } - - return color; - } - - private get bdNegativeHover() { - const color = this.bdNegative.clone(); - - // Lightness of bdNegative is known, no additional checks like in bdNeutralHover - - color.oklch.l = color.oklch.l + 0.15; - - if (color.oklch.c < 0.19) { - color.oklch.c = 0.19; - } - - return color; - } - - private get bdOnNegative() { - // Separator on bgNegative, low contrast to not pull attention from actual separated content elements - const color = this.bgNegative.clone(); - - // Lightness of bgNegative is known, no additional checks like in bdOnAccent / bdOnNeutral - color.oklch.l = this.bgNegative.oklch.l - 0.25; - - return color; - } - - // Desatured version of the seed for harmonious combination with backgrounds and accents. private get bdNeutral() { + // Desatured version of the seed for harmonious combination with backgrounds and accents. const color = this.bdAccent.clone(); color.oklch.c = 0.035; @@ -916,7 +851,7 @@ export class DarkModeTheme implements ColorModeTheme { } if (this.bg.contrastAPCA(color) > -25) { - color.oklch.l = color.oklch.l + 0.15; + color.oklch.l += 0.15; } return color; @@ -926,34 +861,15 @@ export class DarkModeTheme implements ColorModeTheme { const color = this.bdNeutral.clone(); if (this.bdNeutral.oklch.l < 0.8) { - color.oklch.l = color.oklch.l + 0.15; + color.oklch.l += 0.15; } if (this.bdNeutral.oklch.l >= 0.8 && this.bdNeutral.oklch.l < 0.9) { - color.oklch.l = color.oklch.l + 0.1; + color.oklch.l += 0.1; } if (this.bdNeutral.oklch.l >= 0.9) { - color.oklch.l = color.oklch.l - 0.25; - } - - return color; - } - - private get bdOnNeutral() { - // Separator on bgNeutral, low contrast to not pull attention from actual separated content elements - const color = this.bgNeutral.clone(); - - if (this.bgNeutral.oklch.l >= 0.7) { - color.oklch.l = this.bgNeutral.oklch.l - 0.22; - } - - if (this.bgNeutral.oklch.l < 0.7 && this.bgNeutral.oklch.l >= 0.4) { - color.oklch.l = this.bgNeutral.oklch.l - 0.29; - } - - if (this.bgNeutral.oklch.l < 0.4) { - color.oklch.l = this.bgNeutral.oklch.l - 0.36; + color.oklch.l -= 0.25; } return color; @@ -962,18 +878,8 @@ export class DarkModeTheme implements ColorModeTheme { private get bdPositive() { const color = this.bgPositive.clone(); - color.oklch.l = color.oklch.l + 0.05; - color.oklch.c = color.oklch.c + 0.05; - - return color; - } - - private get bdOnWarning() { - // Separator on bgWarning, low contrast to not pull attention from actual separated content elements - const color = this.bgWarning.clone(); - - // Lightness of bgWarning is known, no additional checks like in bdOnAccent / bdOnNeutral - color.oklch.l = this.bgWarning.oklch.l - 0.25; + color.oklch.l += 0.05; + color.oklch.c += 0.05; return color; } @@ -983,17 +889,46 @@ export class DarkModeTheme implements ColorModeTheme { // Lightness of bdPositive is known, no additional checks like in bdNeutralHover - color.oklch.l = color.oklch.l + 0.12; + color.oklch.l += 0.12; return color; } - private get bdOnPositive() { - // Separator on bgPositive, low contrast to not pull attention from actual separated content elements - const color = this.bgPositive.clone(); + private get bdNegative() { + // Negative (red) border. Produced out of bgNegative. Additional compensations are applied if seed is within red range. + const color = this.bgNegative.clone(); - // Lightness of bgPositive is known, no additional checks like in bdOnAccent / bdOnNeutral - color.oklch.l = this.bgPositive.oklch.l - 0.2; + if ( + this.bdAccent.oklch.l > 0.5 && + this.bdAccent.oklch.c > 0.15 && + this.bdAccent.oklch.h < 27 && + this.bdAccent.oklch.h >= 5 + ) { + color.oklch.l += 0.18; + } + + if ( + this.bdAccent.oklch.l > 0.5 && + this.bdAccent.oklch.c > 0.15 && + this.bdAccent.oklch.h >= 27 && + this.bdAccent.oklch.h < 50 + ) { + color.oklch.l += 0.05; + color.oklch.h -= 5; + } + + return color; + } + + private get bdNegativeHover() { + const color = this.bdNegative.clone(); + + // Lightness of bdNegative is known, no additional checks like in bdNeutralHover + color.oklch.l = color.oklch.l + 0.15; + + if (this.bdNegative.oklch.c < 0.19) { + color.oklch.c = 0.19; + } return color; } @@ -1007,7 +942,7 @@ export class DarkModeTheme implements ColorModeTheme { this.bdAccent.oklch.h < 85 && this.bdAccent.oklch.h >= 60 ) { - color.oklch.l = color.oklch.l + 0.18; + color.oklch.l += 0.18; } if ( @@ -1016,8 +951,8 @@ export class DarkModeTheme implements ColorModeTheme { this.bdAccent.oklch.h >= 85 && this.bdAccent.oklch.h < 110 ) { - color.oklch.h = color.oklch.h - 5; - color.oklch.l = color.oklch.l + 0.05; + color.oklch.l += 0.05; + color.oklch.h -= 5; } return color; @@ -1027,19 +962,86 @@ export class DarkModeTheme implements ColorModeTheme { const color = this.bdWarning.clone(); // Lightness of bdWarning is known, no additional checks like in bdNeutralHover - if (this.bdWarning.oklch.l < 0.9) { - color.oklch.l = color.oklch.l + 0.11; + color.oklch.l += 0.11; } if (this.bdWarning.oklch.l >= 0.9) { - color.oklch.l = color.oklch.l - 0.25; + color.oklch.l -= 0.25; } - if (color.oklch.c < 0.19) { + if (this.bdWarning.oklch.c < 0.19) { color.oklch.c = 0.19; } return color; } + + private get bdOnAccent() { + // Separator on bgAccent, low contrast to not pull attention from actual separated content elements + const color = this.bgAccent.clone(); + + if (this.bgAccent.oklch.l >= 0.7) { + color.oklch.l -= 0.22; + } + + if (this.bgAccent.oklch.l < 0.7 && this.bgAccent.oklch.l >= 0.4) { + color.oklch.l -= 0.29; + } + + if (this.bgAccent.oklch.l < 0.4) { + color.oklch.l -= 0.36; + } + + return color; + } + + private get bdOnNeutral() { + // Separator on bgNeutral, low contrast to not pull attention from actual separated content elements + const color = this.bgNeutral.clone(); + + if (this.bgNeutral.oklch.l >= 0.7) { + color.oklch.l -= 0.22; + } + + if (this.bgNeutral.oklch.l < 0.7 && this.bgNeutral.oklch.l >= 0.4) { + color.oklch.l -= 0.29; + } + + if (this.bgNeutral.oklch.l < 0.4) { + color.oklch.l -= 0.36; + } + + return color; + } + + private get bdOnPositive() { + // Separator on bgPositive, low contrast to not pull attention from actual separated content elements + const color = this.bgPositive.clone(); + + // Lightness of bgPositive is known, no additional checks like in bdOnAccent / bdOnNeutral + color.oklch.l -= 0.2; + + return color; + } + + private get bdOnNegative() { + // Separator on bgNegative, low contrast to not pull attention from actual separated content elements + const color = this.bgNegative.clone(); + + // Lightness of bgNegative is known, no additional checks like in bdOnAccent / bdOnNeutral + color.oklch.l -= 0.25; + + return color; + } + + private get bdOnWarning() { + // Separator on bgWarning, low contrast to not pull attention from actual separated content elements + const color = this.bgWarning.clone(); + + // Lightness of bgWarning is known, no additional checks like in bdOnAccent / bdOnNeutral + color.oklch.l -= 0.25; + + return color; + } } diff --git a/app/client/packages/design-system/theming/src/color/LightModeTheme.ts b/app/client/packages/design-system/theming/src/color/LightModeTheme.ts index 26ab4d261b..9d49860477 100644 --- a/app/client/packages/design-system/theming/src/color/LightModeTheme.ts +++ b/app/client/packages/design-system/theming/src/color/LightModeTheme.ts @@ -43,7 +43,6 @@ export class LightModeTheme implements ColorModeTheme { public getColors = () => { return { - // bg bg: this.bg.toString(), bgAccent: this.bgAccent.toString(), bgAccentHover: this.bgAccentHover.toString(), @@ -51,6 +50,11 @@ export class LightModeTheme implements ColorModeTheme { bgAccentSubtleHover: this.bgAccentSubtleHover.toString(), bgAccentSubtleActive: this.bgAccentSubtleActive.toString(), bgAssistive: this.bgAssistive.toString(), + bgNeutral: this.bgNeutral.toString(), + bgNeutralHover: this.bgNeutralHover.toString(), + bgNeutralActive: this.bgNeutralActive.toString(), + bgNeutralSubtleHover: this.bgNeutralSubtleHover.toString(), + bgNeutralSubtleActive: this.bgNeutralSubtleActive.toString(), bgPositive: this.bgPositive.to("sRGB").toString(), bgPositiveHover: this.bgPositiveHover.to("sRGB").toString(), bgPositiveActive: this.bgPositiveActive.to("sRGB").toString(), @@ -66,40 +70,36 @@ export class LightModeTheme implements ColorModeTheme { bgWarningActive: this.bgWarningActive.to("sRGB").toString(), bgWarningSubtleHover: this.bgWarningSubtleHover.to("sRGB").toString(), bgWarningSubtleActive: this.bgWarningSubtleActive.to("sRGB").toString(), - bgNeutral: this.bgNeutral.toString(), - bgNeutralHover: this.bgNeutralHover.toString(), - bgNeutralActive: this.bgNeutralActive.toString(), - bgNeutralSubtle: this.bgNeutralSubtle.toString(), - bgNeutralSubtleHover: this.bgNeutralSubtleHover.toString(), - bgNeutralSubtleActive: this.bgNeutralSubtleActive.toString(), - // fg + fg: this.fg.toString(), fgAccent: this.fgAccent.toString(), - fgOnAccent: this.fgOnAccent.toString(), - fgPositive: this.fgPositive.to("sRGB").toString(), - fgOnPositive: this.fgOnPositive.to("sRGB").toString(), - fgNegative: this.fgNegative.to("sRGB").toString(), - fgOnNegative: this.fgOnNegative.to("sRGB").toString(), fgNeutral: this.fgNeutral.toString(), - fgOnNeutral: this.fgOnNeutral.to("sRGB").toString(), + fgPositive: this.fgPositive.to("sRGB").toString(), + fgNegative: this.fgNegative.to("sRGB").toString(), fgWarning: this.fgWarning.to("sRGB").toString(), - fgOnWarning: this.fgOnWarning.to("sRGB").toString(), + + fgOnAccent: this.fgOnAccent.toString(), fgOnAssistive: this.fgOnAssistive.to("sRGB").toString(), - // bd + fgOnNeutral: this.fgOnNeutral.to("sRGB").toString(), + fgOnPositive: this.fgOnPositive.to("sRGB").toString(), + fgOnNegative: this.fgOnNegative.to("sRGB").toString(), + fgOnWarning: this.fgOnWarning.to("sRGB").toString(), + bdAccent: this.bdAccent.toString(), - bdOnAccent: this.bdOnAccent.toString(), bdFocus: this.bdFocus.toString(), - bdNegative: this.bdNegative.to("sRGB").toString(), - bdNegativeHover: this.bdNegativeHover.to("sRGB").toString(), - bdOnNegative: this.bdOnNegative.to("sRGB").toString(), bdNeutral: this.bdNeutral.toString(), bdNeutralHover: this.bdNeutralHover.toString(), - bdOnNeutral: this.bdOnNeutral.to("sRGB").toString(), bdPositive: this.bdPositive.to("sRGB").toString(), bdPositiveHover: this.bdPositiveHover.to("sRGB").toString(), - bdOnPositive: this.bdOnPositive.to("sRGB").toString(), + bdNegative: this.bdNegative.to("sRGB").toString(), + bdNegativeHover: this.bdNegativeHover.to("sRGB").toString(), bdWarning: this.bdWarning.to("sRGB").toString(), bdWarningHover: this.bdWarningHover.to("sRGB").toString(), + + bdOnAccent: this.bdOnAccent.toString(), + bdOnNeutral: this.bdOnNeutral.to("sRGB").toString(), + bdOnPositive: this.bdOnPositive.to("sRGB").toString(), + bdOnNegative: this.bdOnNegative.to("sRGB").toString(), bdOnWarning: this.bdOnWarning.to("sRGB").toString(), }; }; @@ -108,10 +108,10 @@ export class LightModeTheme implements ColorModeTheme { * Background colors */ - // Main application background color. - // Applies to canvas. In light mode it is extremely light (and therefore desatured) tint of user-set seed color. - // This ensures harmonious combination with main accents and neutrals. private get bg() { + // Main application background color. + // Applies to canvas. In light mode it is extremely light (and therefore desatured) tint of user-set seed color. + // This ensures harmonious combination with main accents and neutrals. const color = this.seedColor.clone(); // For very light seeds set bg darker than usually, so that accent surfaces are clearly visible against it. @@ -140,8 +140,8 @@ export class LightModeTheme implements ColorModeTheme { return color; } - // Main accent color. Largely is the same as user-set seed color. private get bgAccent() { + // Main accent color. Largely is the same as user-set seed color. const color = this.seedColor.clone(); // If seed is very light, make bg darker than usual (see above). Make sure then, that the accent is bright enough. @@ -152,17 +152,17 @@ export class LightModeTheme implements ColorModeTheme { return color; } - // Hover state of bgAccent. Slightly lighter than the resting state to produce the effect of moving closer to the viewer / inspection. private get bgAccentHover() { + // Hover state of bgAccent. Slightly lighter than the resting state to produce the effect of moving closer to the viewer / inspection. const color = this.bgAccent.clone(); // “Slightly lighter” is very dependent on the initial amount of lightness as well as how light (or dark) the surroundings are. if (this.seedLightness < 0.06) { - color.oklch.l = this.bgAccent.oklch.l + 0.28; + color.oklch.l += 0.28; } if (this.seedLightness > 0.06 && this.seedLightness < 0.14) { - color.oklch.l = this.bgAccent.oklch.l + 0.2; + color.oklch.l += 0.2; } if ( @@ -170,7 +170,7 @@ export class LightModeTheme implements ColorModeTheme { this.seedLightness < 0.21 && this.seedIsCold ) { - color.oklch.l = this.bgAccent.oklch.l + 0.1; + color.oklch.l += 0.1; } // Warm colors require a little bit more lightness in this range than colds to be sufficiently perceptually lighter. @@ -179,67 +179,64 @@ export class LightModeTheme implements ColorModeTheme { this.seedLightness < 0.21 && !this.seedIsCold ) { - color.oklch.l = this.bgAccent.oklch.l + 0.13; + color.oklch.l += 0.13; } if (this.seedLightness >= 0.21 && this.seedLightness < 0.4) { - color.oklch.l = this.bgAccent.oklch.l + 0.09; + color.oklch.l += 0.09; } if (this.seedLightness >= 0.4 && this.seedLightness < 0.7) { - color.oklch.l = this.bgAccent.oklch.l + 0.05; + color.oklch.l += 0.05; } if (this.seedLightness >= 0.7) { - color.oklch.l = this.bgAccent.oklch.l + 0.03; + color.oklch.l += 0.03; } // For very light seeds it's impossible to produce hover state that is sufficiently perceptibly lighter, therefore switching to darker hovers. // Yellow has largest amount of chroma available at the top (by lightness) of OKLCh space, compensating by slightly decreasing chroma and decreasing lightness. if (this.seedIsVeryLight && this.seedIsYellow) { color.oklch.l = 0.945; - color.oklch.c = this.seedChroma * 0.93; - color.oklch.h = this.seedHue; + color.oklch.c *= 0.93; } if (this.seedIsVeryLight && !this.seedIsYellow) { color.oklch.l = 0.95; - color.oklch.c = this.seedChroma * 1.15; - color.oklch.h = this.seedHue; + color.oklch.c *= 1.15; } return color; } - // Active state of bgAccent. Slightly darker than the resting state to produce the effect of moving further from the viewer / being pushed down. private get bgAccentActive() { + // Active state of bgAccent. Slightly darker than the resting state to produce the effect of moving further from the viewer / being pushed down. const color = this.bgAccent.clone(); // “Slightly darker” is very dependent on the initial amount of lightness as well as how light (or dark) the surroundings are. if (this.seedLightness < 0.4) { - color.oklch.l = this.bgAccent.oklch.l - 0.04; + color.oklch.l -= 0.04; } if (this.seedLightness >= 0.4 && this.seedLightness < 0.7) { - color.oklch.l = this.bgAccent.oklch.l - 0.02; + color.oklch.l -= 0.02; } if (this.seedLightness >= 0.7) { - color.oklch.l = this.bgAccent.oklch.l - 0.01; + color.oklch.l -= 0.01; } // For very light seeds complement the effect with increased chroma. if (this.seedIsVeryLight) { color.oklch.l = 0.935; - color.oklch.c = this.seedChroma * 1.15; - color.oklch.h = this.seedHue; + color.oklch.c *= 1.15; } return color; } - // Subtle variant of bgAccent. Lighter and less saturated. private get bgAccentSubtle() { + // Subtle variant of bgAccent. Lighter and less saturated. const color = this.seedColor.clone(); if (this.seedIsVeryLight) { @@ -269,7 +266,7 @@ export class LightModeTheme implements ColorModeTheme { private get bgAccentSubtleHover() { const color = this.bgAccentSubtle.clone(); - color.oklch.l = color.oklch.l + 0.02; + color.oklch.l += 0.02; return color; } @@ -277,202 +274,36 @@ export class LightModeTheme implements ColorModeTheme { private get bgAccentSubtleActive() { const color = this.bgAccentSubtle.clone(); - color.oklch.l = color.oklch.l - 0.01; + color.oklch.l -= 0.01; return color; } - // Positive background, green. - private get bgPositive() { - const color = new Color("oklch", [0.62, 0.19, 145]); + private get bgAssistive() { + const color = this.seedColor.clone(); - // If the seed color is also green, adjust positive by hue to make it distinct from accent. - if (this.seedIsGreen && this.seedColor.oklch.c > 0.11) { - if (this.seedColor.oklch.h < 145) { - color.oklch.h = 155; - } - if (this.seedColor.oklch.h >= 145) { - color.oklch.h = 135; - } + // Background color for assistive UI elements (e.g. tooltip); dark to stand out against bg + color.oklch.l = 0.16; + color.oklch.c = 0.07; + + if (this.seedIsAchromatic) { + color.oklch.c = 0; } return color; } - private get bgPositiveHover() { - const color = this.bgPositive.clone(); - - // Lightness of bgPositive is known, no additional checks like in bgAccentHover - color.oklch.l = color.oklch.l + 0.05; - - return color; - } - - private get bgPositiveActive() { - const color = this.bgPositive.clone(); - - // Lightness of bgPositive is known, no additional checks like in bgAccentActive - color.oklch.l = color.oklch.l - 0.02; - - return color; - } - - private get bgPositiveSubtle() { - const color = this.bgPositive.clone(); - - color.oklch.l = 0.955; - color.oklch.c = 0.08; - - return color; - } - - private get bgPositiveSubtleHover() { - const color = this.bgPositiveSubtle.clone(); - - color.oklch.l = color.oklch.l + 0.02; - - return color; - } - - private get bgPositiveSubtleActive() { - const color = this.bgPositiveSubtle.clone(); - - color.oklch.l = color.oklch.l - 0.01; - - return color; - } - - // Warning background, yellow - private get bgWarning() { - const color = new Color("oklch", [0.75, 0.15, 85]); - - // Check for clashes with seed, adjust by hue to make it distinct - if (this.seedIsYellow && this.seedColor.oklch.c > 0.09) { - if (this.seedColor.oklch.h < 85) { - color.oklch.h = 95; - } - if (this.seedColor.oklch.h >= 85) { - color.oklch.h = 70; - } - } - - return color; - } - - private get bgWarningHover() { - const color = this.bgWarning.clone(); - - // Lightness of bgWarning is known, no additional checks like in bgAccentHover - color.oklch.l = color.oklch.l + 0.03; - - return color; - } - - private get bgWarningActive() { - const color = this.bgWarning.clone(); - - // Lightness of bgWarning is known, no additional checks like in bgAccentActive - color.oklch.l = color.oklch.l - 0.01; - - return color; - } - - private get bgWarningSubtle() { - const color = this.bgWarning.clone(); - - color.oklch.l = 0.96; - color.oklch.c = 0.05; - - return color; - } - - private get bgWarningSubtleHover() { - const color = this.bgWarningSubtle.clone(); - - color.oklch.l = color.oklch.l + 0.02; - - return color; - } - - private get bgWarningSubtleActive() { - const color = this.bgWarningSubtle.clone(); - - color.oklch.l = color.oklch.l - 0.01; - - return color; - } - - // Negative background, red. - private get bgNegative() { - const color = new Color("oklch", [0.55, 0.22, 27]); - - // If seed is red adjust negative by hue to make it distinct - if (this.seedIsRed && this.seedColor.oklch.c > 0.12) { - if (this.seedColor.oklch.h < 27) { - color.oklch.h = 34; - } - if (this.seedColor.oklch.h >= 27) { - color.oklch.h = 20; - } - } - - return color; - } - - private get bgNegativeHover() { - const color = this.bgNegative.clone(); - - // Lightness of bgNegative is known, no additional checks like in bgAccentHover - color.oklch.l = color.oklch.l + 0.05; - - return color; - } - - private get bgNegativeActive() { - const color = this.bgNegative.clone(); - - // Lightness of bgNegative is known, no additional checks like in bgAccentActive - color.oklch.l = color.oklch.l - 0.02; - - return color; - } - - private get bgNegativeSubtle() { - const color = this.bgNegative.clone(); - - color.oklch.l = 0.95; - color.oklch.c = 0.05; - - return color; - } - - private get bgNegativeSubtleHover() { - const color = this.bgNegativeSubtle.clone(); - - color.oklch.l = color.oklch.l + 0.02; - - return color; - } - - private get bgNegativeSubtleActive() { - const color = this.bgNegativeSubtle.clone(); - - color.oklch.l = color.oklch.l - 0.01; - - return color; - } - - // Low chroma, but not 0, if possible, to produce harmony with accents in the UI private get bgNeutral() { + // Low chroma, but not 0, if possible, to produce harmony with accents in the UI const color = this.bgAccent.clone(); // For bright accents it helps to make neutral a bit darker to differentiate with bgAccent if (this.bgAccent.oklch.l >= 0.85) { - color.oklch.l = color.oklch.l - 0.02; + color.oklch.l -= 0.02; } if (this.bgAccent.oklch.l > 0.25 && this.bgAccent.oklch.l < 0.85) { - color.oklch.l = color.oklch.l - 0.1; + color.oklch.l -= 0.1; } if (this.seedIsAchromatic) { @@ -496,23 +327,23 @@ export class LightModeTheme implements ColorModeTheme { // Simplified and adjusted version of bgAccentHover algorithm (bgNeutral has very low or no chroma) if (this.bgNeutral.oklch.l < 0.06) { - color.oklch.l = color.oklch.l + 0.24; + color.oklch.l += 0.24; } if (this.bgNeutral.oklch.l > 0.06 && this.bgNeutral.oklch.l < 0.14) { - color.oklch.l = color.oklch.l + 0.14; + color.oklch.l += 0.14; } if (this.bgNeutral.oklch.l >= 0.14 && this.bgNeutral.oklch.l < 0.21) { - color.oklch.l = color.oklch.l + 0.07; + color.oklch.l += 0.07; } if (this.bgNeutral.oklch.l >= 0.21 && this.bgNeutral.oklch.l < 0.7) { - color.oklch.l = color.oklch.l + 0.05; + color.oklch.l += 0.05; } if (this.bgNeutral.oklch.l >= 0.7 && this.bgNeutral.oklch.l < 0.955) { - color.oklch.l = color.oklch.l + 0.03; + color.oklch.l += 0.03; } if (this.bgNeutral.oklch.l >= 0.955) { @@ -526,13 +357,12 @@ export class LightModeTheme implements ColorModeTheme { const color = this.bgNeutral.clone(); // Simplified and adjusted version of bgAccentActive algorithm (bgNeutral has very low or no chroma) - if (this.bgNeutral.oklch.l < 0.4) { - color.oklch.l = color.oklch.l - 0.03; + color.oklch.l -= 0.03; } if (this.bgNeutral.oklch.l >= 0.4 && this.bgNeutral.oklch.l < 0.955) { - color.oklch.l = color.oklch.l - 0.01; + color.oklch.l -= 0.01; } if (this.bgNeutral.oklch.l >= 0.955) { @@ -546,7 +376,6 @@ export class LightModeTheme implements ColorModeTheme { const color = this.seedColor.clone(); // Adjusted version of bgAccentSubtle (less or no chroma) - if (this.seedIsVeryLight) { color.oklch.l = 0.955; } @@ -569,7 +398,7 @@ export class LightModeTheme implements ColorModeTheme { private get bgNeutralSubtleHover() { const color = this.bgNeutralSubtle.clone(); - color.oklch.l = color.oklch.l + 0.02; + color.oklch.l += 0.02; return color; } @@ -577,73 +406,242 @@ export class LightModeTheme implements ColorModeTheme { private get bgNeutralSubtleActive() { const color = this.bgNeutralSubtle.clone(); - color.oklch.l = color.oklch.l - 0.01; + color.oklch.l -= 0.01; return color; } - private get bgAssistive() { - const color = this.seedColor.clone(); + private get bgPositive() { + // Positive background, green. + const color = new Color("oklch", [0.62, 0.19, 145]); - // Background color for assistive UI elements (e.g. tooltip); dark to stand out against bg - color.oklch.l = 0.16; - color.oklch.c = 0.07; - - if (this.seedIsAchromatic) { - color.oklch.c = 0; + // If the seed color is also green, adjust positive by hue to make it distinct from accent. + if (this.seedIsGreen && this.seedChroma > 0.11) { + if (this.seedHue < 145) { + color.oklch.h = 155; + } + if (this.seedHue >= 145) { + color.oklch.h = 135; + } } return color; } + private get bgPositiveHover() { + const color = this.bgPositive.clone(); + + // Lightness of bgPositive is known, no additional checks like in bgAccentHover + color.oklch.l += 0.05; + + return color; + } + + private get bgPositiveActive() { + const color = this.bgPositive.clone(); + + // Lightness of bgPositive is known, no additional checks like in bgAccentActive + color.oklch.l -= 0.02; + + return color; + } + + private get bgPositiveSubtle() { + const color = this.bgPositive.clone(); + + color.oklch.l = 0.955; + color.oklch.c = 0.08; + + return color; + } + + private get bgPositiveSubtleHover() { + const color = this.bgPositiveSubtle.clone(); + + color.oklch.l += 0.02; + + return color; + } + + private get bgPositiveSubtleActive() { + const color = this.bgPositiveSubtle.clone(); + + color.oklch.l -= 0.01; + + return color; + } + + private get bgNegative() { + // Negative background, red. + const color = new Color("oklch", [0.55, 0.22, 27]); + + // If seed is red adjust negative by hue to make it distinct + if (this.seedIsRed && this.seedChroma > 0.12) { + if (this.seedHue < 27) { + color.oklch.h = 34; + } + if (this.seedHue >= 27) { + color.oklch.h = 20; + } + } + + return color; + } + + private get bgNegativeHover() { + const color = this.bgNegative.clone(); + + // Lightness of bgNegative is known, no additional checks like in bgAccentHover + color.oklch.l += 0.05; + + return color; + } + + private get bgNegativeActive() { + const color = this.bgNegative.clone(); + + // Lightness of bgNegative is known, no additional checks like in bgAccentActive + color.oklch.l -= 0.02; + + return color; + } + + private get bgNegativeSubtle() { + const color = this.bgNegative.clone(); + + color.oklch.l = 0.95; + color.oklch.c = 0.05; + + return color; + } + + private get bgNegativeSubtleHover() { + const color = this.bgNegativeSubtle.clone(); + + color.oklch.l += 0.02; + + return color; + } + + private get bgNegativeSubtleActive() { + const color = this.bgNegativeSubtle.clone(); + + color.oklch.l -= 0.01; + + return color; + } + + private get bgWarning() { + // Warning background, yellow + const color = new Color("oklch", [0.75, 0.15, 85]); + + // Check for clashes with seed, adjust by hue to make it distinct + if (this.seedIsYellow && this.seedChroma > 0.09) { + if (this.seedHue < 85) { + color.oklch.h = 95; + } + if (this.seedHue >= 85) { + color.oklch.h = 70; + } + } + + return color; + } + + private get bgWarningHover() { + const color = this.bgWarning.clone(); + + // Lightness of bgWarning is known, no additional checks like in bgAccentHover + color.oklch.l += 0.03; + + return color; + } + + private get bgWarningActive() { + const color = this.bgWarning.clone(); + + // Lightness of bgWarning is known, no additional checks like in bgAccentActive + color.oklch.l -= 0.01; + + return color; + } + + private get bgWarningSubtle() { + const color = this.bgWarning.clone(); + + color.oklch.l = 0.96; + color.oklch.c = 0.05; + + return color; + } + + private get bgWarningSubtleHover() { + const color = this.bgWarningSubtle.clone(); + + color.oklch.l += 0.02; + + return color; + } + + private get bgWarningSubtleActive() { + const color = this.bgWarningSubtle.clone(); + + color.oklch.l -= 0.01; + + return color; + } + /* * Foreground colors */ - // Main application foreground color. - // Applies to static text and similar. In light mode it is extremely dark (and therefore desatured) shade of user-set seed color. - // This ensures harmonious combination with main accents and neutrals. + private get fg() { + // Main application foreground color. + // Applies to static text and similar. In light mode it is extremely dark (and therefore desatured) shade of user-set seed color. + // This ensures harmonious combination with main accents and neutrals. const color = this.seedColor.clone(); + color.oklch.l = 0.12; + // If seed color didn't have substantial amount of chroma make sure fg is achromatic. if (this.seedIsAchromatic) { - color.oklch.l = 0.12; color.oklch.c = 0; - return color; } - color.oklch.l = 0.12; - color.oklch.c = 0.032; + if (!this.seedIsAchromatic) { + color.oklch.c = 0.032; + } + return color; } - // Accent foreground/content color. private get fgAccent() { + // Accent foreground/content color. const color = this.seedColor.clone(); // For dark content on light background APCA contrast is positive. 60 is “The minimum level recommended for content text that is not body, column, or block text. In other words, text you want people to read.” Failure to reach this contrast level is most likely due to high lightness. Lightness and chroma are set to ones that reach the threshold universally regardless of hue. if (this.bg.contrastAPCA(this.seedColor) <= 60) { + color.oklch.l = 0.45; + if (this.seedIsAchromatic) { - color.oklch.l = 0.45; color.oklch.c = 0; - return color; } - color.oklch.l = 0.45; - color.oklch.c = 0.164; - return color; + if (!this.seedIsAchromatic) { + color.oklch.c = 0.164; + } } return color; } - // Desatured version of the seed for harmonious combination with backgrounds and accents. private get fgNeutral() { + // Desatured version of the seed for harmonious combination with backgrounds and accents. const color = this.fgAccent.clone(); // Minimal contrast that we set for fgAccent (60) is too low for a gray color if (this.bg.contrastAPCA(this.fgAccent) < 75) { - color.oklch.l = color.oklch.l - 0.1; + color.oklch.l -= 0.1; } if (this.seedIsAchromatic) { @@ -661,8 +659,8 @@ export class LightModeTheme implements ColorModeTheme { return color; } - // Positive foreground is produced from the initially adjusted background color (see above). Additional tweaks are applied to make sure it's distinct from fgAccent when seed is green. private get fgPositive() { + // Positive foreground is produced from the initially adjusted background color (see above). Additional tweaks are applied to make sure it's distinct from fgAccent when seed is green. const color = this.bgPositive.clone(); if ( @@ -671,33 +669,21 @@ export class LightModeTheme implements ColorModeTheme { this.fgAccent.oklch.l > 0.5 && this.fgAccent.oklch.h < 145 ) { - color.oklch.c = color.oklch.c + 0.05; - color.oklch.h = color.oklch.h - 10; + color.oklch.c += 0.05; + color.oklch.h -= 10; } return color; } - // Warning foreground is produced from the initially adjusted background color (see above). - private get fgWarning() { - const color = this.bgWarning.clone(); - - // Yellow hue interval in OKLCh is less symmetrical than green, compensation is applied to results of bgNegative - color.oklch.l = color.oklch.l - 0.1; - color.oklch.c = color.oklch.c + 0.1; - color.oklch.h = color.oklch.h - 9; - - return color; - } - - // Negative foreground is produced from the initially adjusted background color (see above). Additional tweaks are applied to make sure it's distinct from fgAccent when seed is red. private get fgNegative() { + // Negative foreground is produced from the initially adjusted background color (see above). Additional tweaks are applied to make sure it's distinct from fgAccent when seed is red. const color = this.bgNegative.clone(); // Red hue interval bgNegativein OKLCh is less symmetrical than green, compensation is applied to results of bgNegative - color.oklch.l = color.oklch.l + 0.1; - color.oklch.c = color.oklch.c + 0.1; - color.oklch.h = color.oklch.h - 10; + color.oklch.l += 0.1; + color.oklch.c += 0.1; + color.oklch.h -= 10; if ( this.seedIsRed && @@ -705,15 +691,27 @@ export class LightModeTheme implements ColorModeTheme { this.fgAccent.oklch.l > 0.5 && this.fgAccent.oklch.h < 27 ) { - color.oklch.c = color.oklch.c + 0.05; - color.oklch.h = color.oklch.h - 10; + color.oklch.c += 0.05; + color.oklch.h -= 10; } return color; } - // Foreground for content on top of bgAccent + private get fgWarning() { + // Warning foreground is produced from the initially adjusted background color (see above). + const color = this.bgWarning.clone(); + + // Yellow hue interval in OKLCh is less symmetrical than green, compensation is applied to results of bgNegative + color.oklch.l -= 0.1; + color.oklch.c += 0.1; + color.oklch.h -= 9; + + return color; + } + private get fgOnAccent() { + // Foreground for content on top of bgAccent const tint = this.seedColor.clone(); const shade = this.seedColor.clone(); @@ -736,6 +734,15 @@ export class LightModeTheme implements ColorModeTheme { return shade; } + private get fgOnAssistive() { + // Unlike fgOnAccent we know that bgAssistive is dark in light mode + const tint = this.bgAssistive.clone(); + + tint.oklch.l = 0.97; + + return tint; + } + private get fgOnNeutral() { // Simplified and adjusted version of fgOnAccent const tint = this.bgNeutral.clone(); @@ -774,25 +781,6 @@ export class LightModeTheme implements ColorModeTheme { return shade; } - private get fgOnWarning() { - // Simplified and adjusted version of fgOnAccent - const tint = this.bgWarning.clone(); - const shade = this.bgWarning.clone(); - - // Light and dark derivatives of the bgWarning - tint.oklch.l = 0.95; - shade.oklch.l = 0.25; - - // Check which of them has better contrast with bgWarning - if ( - -this.bgWarning.contrastAPCA(tint) >= this.bgWarning.contrastAPCA(shade) - ) { - return tint; - } - - return shade; - } - private get fgOnNegative() { // Simplified and adjusted version of fgOnAccent const tint = this.bgNegative.clone(); @@ -812,20 +800,31 @@ export class LightModeTheme implements ColorModeTheme { return shade; } - private get fgOnAssistive() { - // Unlike fgOnAccent we know that bgAssistive is dark in light mode - const tint = this.bgAssistive.clone(); + private get fgOnWarning() { + // Simplified and adjusted version of fgOnAccent + const tint = this.bgWarning.clone(); + const shade = this.bgWarning.clone(); - tint.oklch.l = 0.97; + // Light and dark derivatives of the bgWarning + tint.oklch.l = 0.95; + shade.oklch.l = 0.25; - return tint; + // Check which of them has better contrast with bgWarning + if ( + -this.bgWarning.contrastAPCA(tint) >= this.bgWarning.contrastAPCA(shade) + ) { + return tint; + } + + return shade; } /* * Border colors */ - // Accent border color + private get bdAccent() { + // Accent border color const color = this.seedColor.clone(); // For dark content on light background APCA contrast is positive. 15 is “The absolute minimum for any non-text that needs to be discernible and differentiable, but does not apply to semantic non-text such as icons”. In practice, thin borders are perceptually too subtle when using this as a threshould. 25 is used as the required minimum instead. Failure to reach this contrast level is most likely due to high lightness. Lightness and chroma are set to ones that reach the threshold universally regardless of hue. @@ -833,35 +832,12 @@ export class LightModeTheme implements ColorModeTheme { if (this.seedIsAchromatic) { color.oklch.l = 0.3; color.oklch.c = 0; - return color; } - color.oklch.l = 0.55; - color.oklch.c = 0.25; - return color; - } - - return color; - } - - private get bdOnAccent() { - // Separator on bgAccent, low contrast to not pull attention from actual separated content elements - const color = this.bgAccent.clone(); - - if (this.bgAccent.oklch.l >= 0.7) { - color.oklch.l = this.bgAccent.oklch.l - 0.25; - } - - if (this.bgAccent.oklch.l < 0.7 && this.bgAccent.oklch.l >= 0.4) { - color.oklch.l = this.bgAccent.oklch.l - 0.33; - } - - if (this.bgAccent.oklch.l < 0.4 && this.bgAccent.oklch.l >= 0.15) { - color.oklch.l = this.bgAccent.oklch.l + 0.2; - } - - if (this.bgAccent.oklch.l < 0.15) { - color.oklch.l = this.bgAccent.oklch.l + 0.46; + if (!this.seedIsAchromatic) { + color.oklch.l = 0.55; + color.oklch.c = 0.25; + } } return color; @@ -885,65 +861,18 @@ export class LightModeTheme implements ColorModeTheme { } // Green-red color blindness is among the most prevalent, so instead of 180 we're rotating hue by additional 60° - color.oklch.h = this.seedHue - 240; + color.oklch.h -= 240; // Additional adjustments for red, pinks, magentas if ((this.seedHue >= 0 && this.seedHue <= 55) || this.seedHue >= 340) { - color.oklch.h = color.oklch.h + 160; + color.oklch.h += 160; } return color; } - // Negative (red) border. Produced out of bgNegative. Additional compensations are applied if seed is within red range. - private get bdNegative() { - const color = this.bgNegative.clone(); - - if ( - this.bdAccent.oklch.l > 0.5 && - this.bdAccent.oklch.c > 0.15 && - this.bdAccent.oklch.h < 27 && - this.bdAccent.oklch.h >= 5 - ) { - color.oklch.h = color.oklch.h + 5; - color.oklch.l = color.oklch.l + 0.1; - } - - if ( - this.bdAccent.oklch.l > 0.5 && - this.bdAccent.oklch.c > 0.15 && - this.bdAccent.oklch.h >= 27 && - this.bdAccent.oklch.h < 50 - ) { - color.oklch.h = color.oklch.h - 5; - color.oklch.l = color.oklch.l + 0.05; - } - - return color; - } - - private get bdNegativeHover() { - const color = this.bdNegative.clone(); - - // Lightness of bdNegative is known, no additional checks like in bdNeutralHover - - color.oklch.l = color.oklch.l + 0.1; - - return color; - } - - private get bdOnNegative() { - // Separator on bgNegative, low contrast to not pull attention from actual separated content elements - const color = this.bgNegative.clone(); - - // Lightness of bgNegative is known, no additional checks like in bdOnAccent / bdOnNeutral - color.oklch.l = this.bgNegative.oklch.l - 0.33; - - return color; - } - - // Desatured version of the seed for harmonious combination with backgrounds and accents. private get bdNeutral() { + // Desatured version of the seed for harmonious combination with backgrounds and accents. const color = this.bdAccent.clone(); color.oklch.c = 0.035; @@ -953,7 +882,7 @@ export class LightModeTheme implements ColorModeTheme { } if (this.bg.contrastAPCA(color) < 25) { - color.oklch.l = color.oklch.l - 0.2; + color.oklch.l -= 0.2; } return color; @@ -963,19 +892,150 @@ export class LightModeTheme implements ColorModeTheme { const color = this.bdNeutral.clone(); if (this.bdNeutral.oklch.l < 0.06) { - color.oklch.l = color.oklch.l + 0.6; + color.oklch.l += 0.6; } if (this.bdNeutral.oklch.l >= 0.06 && this.bdNeutral.oklch.l < 0.25) { - color.oklch.l = color.oklch.l + 0.4; + color.oklch.l += 0.4; } if (this.bdNeutral.oklch.l >= 0.25 && this.bdNeutral.oklch.l < 0.5) { - color.oklch.l = color.oklch.l + 0.25; + color.oklch.l += 0.25; } if (this.bdNeutral.oklch.l >= 0.5) { - color.oklch.l = color.oklch.l + 0.1; + color.oklch.l += 0.1; + } + + return color; + } + + private get bdPositive() { + // Positive (green) border. Additional compensations are applied if seed is withing green range. + const color = this.bgPositive.clone(); + + if ( + this.bdAccent.oklch.l > 0.5 && + this.bdAccent.oklch.c > 0.11 && + this.bdAccent.oklch.h < 145 && + this.bdAccent.oklch.h >= 116 + ) { + color.oklch.l += 0.1; + color.oklch.h += 5; + } + + if ( + this.bdAccent.oklch.l > 0.5 && + this.bdAccent.oklch.c > 0.11 && + this.bdAccent.oklch.h >= 145 && + this.bdAccent.oklch.h < 166 + ) { + color.oklch.l += 0.05; + color.oklch.h -= 5; + } + + return color; + } + + private get bdPositiveHover() { + const color = this.bdPositive.clone(); + + // Lightness of bdPositive is known, no additional checks like in bdNeutralHover + color.oklch.l += 0.1; + + return color; + } + + private get bdNegative() { + // Negative (red) border. Produced out of bgNegative. Additional compensations are applied if seed is within red range. + const color = this.bgNegative.clone(); + + if ( + this.bdAccent.oklch.l > 0.5 && + this.bdAccent.oklch.c > 0.15 && + this.bdAccent.oklch.h < 27 && + this.bdAccent.oklch.h >= 5 + ) { + color.oklch.l += 0.1; + color.oklch.h += 5; + } + + if ( + this.bdAccent.oklch.l > 0.5 && + this.bdAccent.oklch.c > 0.15 && + this.bdAccent.oklch.h >= 27 && + this.bdAccent.oklch.h < 50 + ) { + color.oklch.l += 0.05; + color.oklch.h -= 5; + } + + return color; + } + + private get bdNegativeHover() { + const color = this.bdNegative.clone(); + + // Lightness of bdNegative is known, no additional checks like in bdNeutralHover + color.oklch.l += 0.1; + + return color; + } + + private get bdWarning() { + // Warning (yellow) border. Produced out of bgNegative. Additional compensations are applied if seed is within yellow range. + const color = this.bgWarning.clone(); + + if ( + this.bdAccent.oklch.l > 0.5 && + this.bdAccent.oklch.c > 0.09 && + this.bdAccent.oklch.h < 85 && + this.bdAccent.oklch.h >= 60 + ) { + color.oklch.l += 0.1; + color.oklch.h += 10; + } + + if ( + this.bdAccent.oklch.l > 0.5 && + this.bdAccent.oklch.c > 0.09 && + this.bdAccent.oklch.h >= 85 && + this.bdAccent.oklch.h < 110 + ) { + color.oklch.l += 0.05; + color.oklch.h -= 10; + } + + return color; + } + + private get bdWarningHover() { + const color = this.bdWarning.clone(); + + // Lightness of bdWarning is known, no additional checks like in bdNeutralHover + color.oklch.l += 0.1; + + return color; + } + + private get bdOnAccent() { + // Separator on bgAccent, low contrast to not pull attention from actual separated content elements + const color = this.bgAccent.clone(); + + if (this.bgAccent.oklch.l >= 0.7) { + color.oklch.l -= 0.25; + } + + if (this.bgAccent.oklch.l < 0.7 && this.bgAccent.oklch.l >= 0.4) { + color.oklch.l -= 0.33; + } + + if (this.bgAccent.oklch.l < 0.4 && this.bgAccent.oklch.l >= 0.15) { + color.oklch.l += 0.2; + } + + if (this.bgAccent.oklch.l < 0.15) { + color.oklch.l += 0.46; } return color; @@ -986,104 +1046,40 @@ export class LightModeTheme implements ColorModeTheme { const color = this.bgNeutral.clone(); if (this.bgNeutral.oklch.l >= 0.7) { - color.oklch.l = this.bgNeutral.oklch.l - 0.28; + color.oklch.l -= 0.28; } if (this.bgNeutral.oklch.l < 0.7 && this.bgNeutral.oklch.l >= 0.4) { - color.oklch.l = this.bgNeutral.oklch.l - 0.35; + color.oklch.l -= 0.35; } if (this.bgNeutral.oklch.l < 0.4 && this.bgNeutral.oklch.l >= 0.15) { - color.oklch.l = this.bgNeutral.oklch.l + 0.22; + color.oklch.l += 0.22; } if (this.bgNeutral.oklch.l < 0.15) { - color.oklch.l = this.bgNeutral.oklch.l + 0.47; + color.oklch.l += 0.47; } return color; } - // Positive (green) border. Additional compensations are applied if seed is withing green range. - private get bdPositive() { - const color = this.bgPositive.clone(); - - if ( - this.bdAccent.oklch.l > 0.5 && - this.bdAccent.oklch.c > 0.11 && - this.bdAccent.oklch.h < 145 && - this.bdAccent.oklch.h >= 116 - ) { - color.oklch.h = color.oklch.h + 5; - color.oklch.l = color.oklch.l + 0.1; - } - - if ( - this.bdAccent.oklch.l > 0.5 && - this.bdAccent.oklch.c > 0.11 && - this.bdAccent.oklch.h >= 145 && - this.bdAccent.oklch.h < 166 - ) { - color.oklch.h = color.oklch.h - 5; - color.oklch.l = color.oklch.l + 0.05; - } - - return color; - } - - private get bdPositiveHover() { - const color = this.bdPositive.clone(); - - // Lightness of bdPositive is known, no additional checks like in bdNeutralHover - - color.oklch.l = color.oklch.l + 0.1; - - return color; - } - private get bdOnPositive() { // Separator on bgPositive, low contrast to not pull attention from actual separated content elements const color = this.bgPositive.clone(); // Lightness of bgPositive is known, no additional checks like in bdOnAccent / bdOnNeutral - color.oklch.l = this.bgPositive.oklch.l - 0.33; + color.oklch.l -= 0.33; return color; } - // Warning (yellow) border. Produced out of bgNegative. Additional compensations are applied if seed is within yellow range. - private get bdWarning() { - const color = this.bgWarning.clone(); + private get bdOnNegative() { + // Separator on bgNegative, low contrast to not pull attention from actual separated content elements + const color = this.bgNegative.clone(); - if ( - this.bdAccent.oklch.l > 0.5 && - this.bdAccent.oklch.c > 0.09 && - this.bdAccent.oklch.h < 85 && - this.bdAccent.oklch.h >= 60 - ) { - color.oklch.h = color.oklch.h + 10; - color.oklch.l = color.oklch.l + 0.1; - } - - if ( - this.bdAccent.oklch.l > 0.5 && - this.bdAccent.oklch.c > 0.09 && - this.bdAccent.oklch.h >= 85 && - this.bdAccent.oklch.h < 110 - ) { - color.oklch.h = color.oklch.h - 10; - color.oklch.l = color.oklch.l + 0.05; - } - - return color; - } - - private get bdWarningHover() { - const color = this.bdWarning.clone(); - - // Lightness of bdWarning is known, no additional checks like in bdNeutralHover - - color.oklch.l = color.oklch.l + 0.1; + // Lightness of bgNegative is known, no additional checks like in bdOnAccent / bdOnNeutral + color.oklch.l -= 0.33; return color; } @@ -1093,7 +1089,7 @@ export class LightModeTheme implements ColorModeTheme { const color = this.bgWarning.clone(); // Lightness of bgWarning is known, no additional checks like in bdOnAccent / bdOnNeutral - color.oklch.l = this.bgWarning.oklch.l - 0.33; + color.oklch.l -= 0.33; return color; } diff --git a/app/client/packages/design-system/theming/src/color/types.ts b/app/client/packages/design-system/theming/src/color/types.ts index 399d708e80..c35f38dc69 100644 --- a/app/client/packages/design-system/theming/src/color/types.ts +++ b/app/client/packages/design-system/theming/src/color/types.ts @@ -11,6 +11,12 @@ export interface ColorModeTheme { bgAccentActive: string; bgAccentSubtleHover: string; bgAccentSubtleActive: string; + bgAssistive: string; + bgNeutral: string; + bgNeutralHover: string; + bgNeutralActive: string; + bgNeutralSubtleHover: string; + bgNeutralSubtleActive: string; bgPositive: string; bgPositiveHover: string; bgPositiveActive: string; @@ -26,34 +32,36 @@ export interface ColorModeTheme { bgWarningActive: string; bgWarningSubtleHover: string; bgWarningSubtleActive: string; - bgNeutral: string; - bgNeutralHover: string; - bgNeutralActive: string; - bgNeutralSubtleHover: string; - bgNeutralSubtleActive: string; // fg fg: string; fgAccent: string; - fgNegative: string; fgNeutral: string; fgPositive: string; + fgNegative: string; fgWarning: string; + // fg on bg* fgOnAccent: string; + fgOnAssistive: string; fgOnNeutral: string; fgOnPositive: string; - fgOnWarning: string; fgOnNegative: string; - fgOnAssistive: string; + fgOnWarning: string; // bd bdAccent: string; bdFocus: string; - bdNegative: string; - bdNegativeHover: string; bdNeutral: string; bdNeutralHover: string; bdPositive: string; bdPositiveHover: string; + bdNegative: string; + bdNegativeHover: string; bdWarning: string; bdWarningHover: string; + // bd on bg* + bdOnAccent: string; + bdOnNeutral: string; + bdOnPositive: string; + bdOnNegative: string; + bdOnWarning: string; }; }