a tool for shared writing and social publishing

adjusted colors just ONE MORE TIME

+48 -36
+1 -1
app/(home-pages)/home/LeafletList/LeafletListItem.tsx
··· 57 57 > 58 58 <SpeedyLink 59 59 href={`/${tokenId}`} 60 - className={`absolute w-full h-full top-0 left-0 no-underline hover:no-underline! text-primary`} 60 + className={`absolute w-full h-full top-0 left-0 no-underline! hover:no-underline! text-primary`} 61 61 /> 62 62 {props.showPreview && <LeafletListPreview isVisible={isOnScreen} />} 63 63 <LeafletInfo
-1
components/ThemeManager/PublicationThemeProvider.tsx
··· 2 2 import { useMemo, useState } from "react"; 3 3 import { parseColor } from "react-aria-components"; 4 4 import { useEntity } from "src/replicache"; 5 - import { getColorDifference } from "./themeUtils"; 6 5 import { useColorAttribute, colorToString } from "./useColorAttribute"; 7 6 import { BaseThemeProvider, CardBorderHiddenContext } from "./ThemeProvider"; 8 7 import { PubLeafletPublication, PubLeafletThemeColor } from "lexicons/api";
+31 -30
components/ThemeManager/ThemeProvider.tsx
··· 28 28 PublicationBackgroundProvider, 29 29 PublicationThemeProvider, 30 30 } from "./PublicationThemeProvider"; 31 - import { getColorDifference } from "./themeUtils"; 31 + import { compareColors } from "./themeUtils"; 32 32 import { 33 33 getFontConfig, 34 34 getGoogleFontsUrl, ··· 170 170 !showPageBackground && !hasBackgroundImage ? bgLeaflet : bgPageProp; 171 171 172 172 let accentContrast; 173 + let bgRef = colorToString(showPageBackground ? bgPage : bgLeaflet, "rgb"); 174 + let primaryStr = colorToString(primary, "rgb"); 175 + 173 176 let sortedAccents = [accent1, accent2].sort((a, b) => { 174 177 // sort accents by contrast against the background 175 178 return ( 176 - getColorDifference( 177 - colorToString(b, "rgb"), 178 - colorToString(showPageBackground ? bgPage : bgLeaflet, "rgb"), 179 - ) - 180 - getColorDifference( 181 - colorToString(a, "rgb"), 182 - colorToString(showPageBackground ? bgPage : bgLeaflet, "rgb"), 183 - ) 179 + compareColors(colorToString(b, "rgb"), bgRef).distance - 180 + compareColors(colorToString(a, "rgb"), bgRef).distance 184 181 ); 185 182 }); 183 + 184 + let bestVsText = compareColors( 185 + colorToString(sortedAccents[0], "rgb"), 186 + primaryStr, 187 + ); 188 + let altVsBg = compareColors(colorToString(sortedAccents[1], "rgb"), bgRef); 189 + 186 190 if ( 187 191 // if the contrast-y accent is too similar to text color 188 - getColorDifference( 189 - colorToString(sortedAccents[0], "rgb"), 190 - colorToString(primary, "rgb"), 191 - ) < 0.15 && 192 + // (close in distance AND not distinguishable by hue/chroma) 193 + bestVsText.distance < 0.15 && 194 + bestVsText.chromaDiff < 0.05 && 192 195 // and if the other accent is different enough from the background 193 - getColorDifference( 194 - colorToString(sortedAccents[1], "rgb"), 195 - colorToString(showPageBackground ? bgPage : bgLeaflet, "rgb"), 196 - ) > 0.31 196 + altVsBg.distance > 0.31 197 197 ) { 198 198 //then choose the less contrast-y accent 199 199 accentContrast = sortedAccents[1]; ··· 202 202 accentContrast = sortedAccents[0]; 203 203 } 204 204 205 - // Check if the final accent contrast color is very similar to the text color 205 + // Check if the accent contrast color is visually similar to the text color. 206 + // We check both overall OKLab distance AND chroma (hue/saturation) difference 207 + // because dark colors are compressed in OKLab lightness — a dark blue accent 208 + // vs black text can have a small OKLab distance yet be clearly distinguishable 209 + // by hue. If the chroma difference is significant, the colors are visually 210 + // distinct and don't need an underline to tell them apart. 211 + let accentVsText = compareColors( 212 + colorToString(accentContrast, "rgb"), 213 + primaryStr, 214 + ); 206 215 let accentContrastSimilarToText = 207 - getColorDifference( 208 - colorToString(accentContrast, "rgb"), 209 - colorToString(primary, "rgb"), 210 - ) < 0.2; 216 + accentVsText.distance < 0.45 && accentVsText.chromaDiff < 0.05; 211 217 212 218 // Get font configs for CSS variables. 213 219 // When using the default font (Quattro), use var(--font-quattro) which is ··· 418 424 let accentContrast = 419 425 bgPage && accent1 && accent2 420 426 ? [accent1, accent2].sort((a, b) => { 427 + let bgStr = colorToString(bgPage, "rgb"); 421 428 return ( 422 - getColorDifference( 423 - colorToString(b, "rgb"), 424 - colorToString(bgPage, "rgb"), 425 - ) - 426 - getColorDifference( 427 - colorToString(a, "rgb"), 428 - colorToString(bgPage, "rgb"), 429 - ) 429 + compareColors(colorToString(b, "rgb"), bgStr).distance - 430 + compareColors(colorToString(a, "rgb"), bgStr).distance 430 431 ); 431 432 })[0] 432 433 : null;
+16 -4
components/ThemeManager/themeUtils.ts
··· 1 - import { parse, ColorSpace, sRGB, distance, OKLab } from "colorjs.io/fn"; 1 + import { parse, ColorSpace, sRGB, distance, OKLab, to } from "colorjs.io/fn"; 2 2 3 3 // define the color defaults for everything 4 4 export const ThemeDefaults = { ··· 16 16 "theme/accent-contrast": "#57822B", 17 17 }; 18 18 19 - // used to calculate the contrast between page and accent1, accent2, and determin which is higher contrast 20 - export function getColorDifference(color1: string, color2: string) { 19 + // Compares two RGB color strings in OKLab space and returns both the overall 20 + // perceptual distance and the chroma (hue/saturation) difference. 21 + // 22 + // Why both? Dark colors are compressed in OKLab lightness, so two colors can 23 + // have a small overall distance yet be clearly distinguishable by hue (e.g. 24 + // dark blue vs black). Checking chromaDiff lets callers tell apart "two 25 + // similar grays" from "a dark chromatic color next to a gray/black". 26 + export function compareColors(color1: string, color2: string) { 21 27 ColorSpace.register(sRGB); 22 28 ColorSpace.register(OKLab); 23 29 24 30 let parsedColor1 = parse(`rgb(${color1})`); 25 31 let parsedColor2 = parse(`rgb(${color2})`); 26 32 27 - return distance(parsedColor1, parsedColor2, "oklab"); 33 + let [, a1, b1] = to(parsedColor1, "oklab").coords; 34 + let [, a2, b2] = to(parsedColor2, "oklab").coords; 35 + 36 + return { 37 + distance: distance(parsedColor1, parsedColor2, "oklab"), 38 + chromaDiff: Math.sqrt((a1 - a2) ** 2 + (b1 - b2) ** 2), 39 + }; 28 40 }