diff --git a/app/(home-pages)/home/LeafletList/LeafletListItem.tsx b/app/(home-pages)/home/LeafletList/LeafletListItem.tsx index d647206e..af30a578 100644 --- a/app/(home-pages)/home/LeafletList/LeafletListItem.tsx +++ b/app/(home-pages)/home/LeafletList/LeafletListItem.tsx @@ -57,7 +57,7 @@ export const LeafletListItem = (props: { > {props.showPreview && } { // sort accents by contrast against the background return ( - getColorDifference( - colorToString(b, "rgb"), - colorToString(showPageBackground ? bgPage : bgLeaflet, "rgb"), - ) - - getColorDifference( - colorToString(a, "rgb"), - colorToString(showPageBackground ? bgPage : bgLeaflet, "rgb"), - ) + compareColors(colorToString(b, "rgb"), bgRef).distance - + compareColors(colorToString(a, "rgb"), bgRef).distance ); }); + + let bestVsText = compareColors( + colorToString(sortedAccents[0], "rgb"), + primaryStr, + ); + let altVsBg = compareColors(colorToString(sortedAccents[1], "rgb"), bgRef); + if ( // if the contrast-y accent is too similar to text color - getColorDifference( - colorToString(sortedAccents[0], "rgb"), - colorToString(primary, "rgb"), - ) < 0.15 && + // (close in distance AND not distinguishable by hue/chroma) + bestVsText.distance < 0.15 && + bestVsText.chromaDiff < 0.05 && // and if the other accent is different enough from the background - getColorDifference( - colorToString(sortedAccents[1], "rgb"), - colorToString(showPageBackground ? bgPage : bgLeaflet, "rgb"), - ) > 0.31 + altVsBg.distance > 0.31 ) { //then choose the less contrast-y accent accentContrast = sortedAccents[1]; @@ -202,12 +202,18 @@ export const BaseThemeProvider = ({ accentContrast = sortedAccents[0]; } - // Check if the final accent contrast color is very similar to the text color + // Check if the accent contrast color is visually similar to the text color. + // We check both overall OKLab distance AND chroma (hue/saturation) difference + // because dark colors are compressed in OKLab lightness — a dark blue accent + // vs black text can have a small OKLab distance yet be clearly distinguishable + // by hue. If the chroma difference is significant, the colors are visually + // distinct and don't need an underline to tell them apart. + let accentVsText = compareColors( + colorToString(accentContrast, "rgb"), + primaryStr, + ); let accentContrastSimilarToText = - getColorDifference( - colorToString(accentContrast, "rgb"), - colorToString(primary, "rgb"), - ) < 0.2; + accentVsText.distance < 0.45 && accentVsText.chromaDiff < 0.05; // Get font configs for CSS variables. // When using the default font (Quattro), use var(--font-quattro) which is @@ -418,15 +424,10 @@ export function CardThemeProvider(props: { let accentContrast = bgPage && accent1 && accent2 ? [accent1, accent2].sort((a, b) => { + let bgStr = colorToString(bgPage, "rgb"); return ( - getColorDifference( - colorToString(b, "rgb"), - colorToString(bgPage, "rgb"), - ) - - getColorDifference( - colorToString(a, "rgb"), - colorToString(bgPage, "rgb"), - ) + compareColors(colorToString(b, "rgb"), bgStr).distance - + compareColors(colorToString(a, "rgb"), bgStr).distance ); })[0] : null; diff --git a/components/ThemeManager/themeUtils.ts b/components/ThemeManager/themeUtils.ts index 50ef8866..baf1e8fc 100644 --- a/components/ThemeManager/themeUtils.ts +++ b/components/ThemeManager/themeUtils.ts @@ -1,4 +1,4 @@ -import { parse, ColorSpace, sRGB, distance, OKLab } from "colorjs.io/fn"; +import { parse, ColorSpace, sRGB, distance, OKLab, to } from "colorjs.io/fn"; // define the color defaults for everything export const ThemeDefaults = { @@ -16,13 +16,25 @@ export const ThemeDefaults = { "theme/accent-contrast": "#57822B", }; -// used to calculate the contrast between page and accent1, accent2, and determin which is higher contrast -export function getColorDifference(color1: string, color2: string) { +// Compares two RGB color strings in OKLab space and returns both the overall +// perceptual distance and the chroma (hue/saturation) difference. +// +// Why both? Dark colors are compressed in OKLab lightness, so two colors can +// have a small overall distance yet be clearly distinguishable by hue (e.g. +// dark blue vs black). Checking chromaDiff lets callers tell apart "two +// similar grays" from "a dark chromatic color next to a gray/black". +export function compareColors(color1: string, color2: string) { ColorSpace.register(sRGB); ColorSpace.register(OKLab); let parsedColor1 = parse(`rgb(${color1})`); let parsedColor2 = parse(`rgb(${color2})`); - return distance(parsedColor1, parsedColor2, "oklab"); + let [, a1, b1] = to(parsedColor1, "oklab").coords; + let [, a2, b2] = to(parsedColor2, "oklab").coords; + + return { + distance: distance(parsedColor1, parsedColor2, "oklab"), + chromaDiff: Math.sqrt((a1 - a2) ** 2 + (b1 - b2) ** 2), + }; }