char-slop/berry

multipurpose self-modifiable agentic harness

git clone https://git.t4t.associates/char-slop/berry

Charlotte Somtest and palette cleanups: one source for the 16-color terminal palette, no change-detector testsb3a4fb7

main
1.9 KiB51 linesraw
1import type { Rgb } from "./ghostty.ts";
2import { TERMINAL_PALETTE } from "@berry/util/palette.ts";
3
4const rgb = (hex: string): Rgb => [
5  parseInt(hex.slice(1, 3), 16),
6  parseInt(hex.slice(3, 5), 16),
7  parseInt(hex.slice(5, 7), 16),
8];
9
10/** berry's colors, as programs see them when they ask, and as the view draws their defaults. */
11export const COLORS = {
12  foreground: rgb("#f3eef2"),
13  background: rgb("#100e11"),
14  cursor: rgb("#ff9df5"),
15  palette: TERMINAL_PALETTE.map(rgb),
16};
17
18/** Below this, text is hard to read on its background: ghostty's suggestion for a `minimum-contrast` that ensures it. */
19const MINIMUM_CONTRAST = 3;
20
21const luminance = (color: Rgb) => {
22  const [r, g, b] = color.map((c) => {
23    const s = c / 255;
24    return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
25  });
26  return 0.2126 * r + 0.7152 * g + 0.0722 * b;
27};
28
29/** WCAG 2's contrast ratio between two colors, from 1 for none to 21. */
30export function contrast(a: Rgb, b: Rgb): number {
31  const [lighter, darker] = [luminance(a), luminance(b)].sort((x, y) => y - x);
32  return (lighter + 0.05) / (darker + 0.05);
33}
34
35/**
36 * The color to draw text of `color` in on `background`: itself if it reads there, else moved toward white or black,
37 * whichever stands out more there, just far enough that it does, so black on a dark background is a dark gray.
38 */
39export function readable(color: Rgb, background: Rgb): Rgb {
40  if (contrast(color, background) >= MINIMUM_CONTRAST) return color;
41  const toward = contrast([255, 255, 255], background) >= contrast([0, 0, 0], background) ? 255 : 0;
42  const mixed = (t: number): Rgb => color.map((c) => Math.round(c + (toward - c) * t)) as never;
43  // the least mix that reads, found by halving
44  let [low, high] = [0, 1];
45  for (let i = 0; i < 16; i++) {
46    const middle = (low + high) / 2;
47    if (contrast(mixed(middle), background) >= MINIMUM_CONTRAST) high = middle;
48    else low = middle;
49  }
50  return mixed(high);
51}