Files
NetMesh/domain/colorContrast.ts
zhaolei 3c72efcb7f
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

69 lines
2.4 KiB
TypeScript

type ParsedHslToken = {
hue: number;
saturation: number;
lightness: number;
};
const BLACK_HSL = '0 0% 0%';
const WHITE_HSL = '0 0% 100%';
const parseHslToken = (value: string): ParsedHslToken | null => {
const match = /^\s*(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)%\s+(\d+(?:\.\d+)?)%\s*$/.exec(value);
if (!match) return null;
const hue = Number(match[1]);
const saturation = Number(match[2]);
const lightness = Number(match[3]);
if (![hue, saturation, lightness].every(Number.isFinite)) return null;
return {
hue: ((hue % 360) + 360) % 360,
saturation: Math.min(100, Math.max(0, saturation)) / 100,
lightness: Math.min(100, Math.max(0, lightness)) / 100,
};
};
const hslToRgb = ({ hue, saturation, lightness }: ParsedHslToken): [number, number, number] => {
if (saturation === 0) return [lightness, lightness, lightness];
const chroma = (1 - Math.abs(2 * lightness - 1)) * saturation;
const huePrime = hue / 60;
const x = chroma * (1 - Math.abs((huePrime % 2) - 1));
const [red, green, blue] =
huePrime < 1 ? [chroma, x, 0] :
huePrime < 2 ? [x, chroma, 0] :
huePrime < 3 ? [0, chroma, x] :
huePrime < 4 ? [0, x, chroma] :
huePrime < 5 ? [x, 0, chroma] :
[chroma, 0, x];
const match = lightness - chroma / 2;
return [red + match, green + match, blue + match];
};
const toLinearSrgb = (channel: number): number => (
channel <= 0.03928 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4
);
export const getHslTokenRelativeLuminance = (value: string): number | null => {
const parsed = parseHslToken(value);
if (!parsed) return null;
const [red, green, blue] = hslToRgb(parsed).map(toLinearSrgb);
return 0.2126 * red + 0.7152 * green + 0.0722 * blue;
};
export const getContrastRatio = (foregroundLuminance: number, backgroundLuminance: number): number => {
const lighter = Math.max(foregroundLuminance, backgroundLuminance);
const darker = Math.min(foregroundLuminance, backgroundLuminance);
return (lighter + 0.05) / (darker + 0.05);
};
export const resolveReadableForegroundForHsl = (
backgroundHsl: string,
fallback: string = WHITE_HSL,
): string => {
const backgroundLuminance = getHslTokenRelativeLuminance(backgroundHsl);
if (backgroundLuminance == null) return fallback;
const blackContrast = getContrastRatio(0, backgroundLuminance);
const whiteContrast = getContrastRatio(1, backgroundLuminance);
return whiteContrast >= blackContrast ? WHITE_HSL : BLACK_HSL;
};