diff --git a/files/claude_agents.py b/files/claude_agents.py index 8e99928..7de5a04 100644 --- a/files/claude_agents.py +++ b/files/claude_agents.py @@ -841,8 +841,10 @@ CLIENT_COLORS_JS = """function panelColors(bg) { bg = bg.toLowerCase(); const rgb = h => [1, 3, 5].map(i => parseInt(h.slice(i, i + 2), 16)); const lin = c => (c /= 255) <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4); - const [r, g, b] = rgb(bg).map(lin); // относительная яркость по WCAG - const fg = 0.2126 * r + 0.7152 * g + 0.0722 * b > 0.4 ? '__LIGHT__' : '__DARK__'; + const lum = h => { const [r, g, b] = rgb(h).map(lin); return 0.2126 * r + 0.7152 * g + 0.0722 * b; }; + const ratio = (a, c) => (Math.max(a, c) + 0.05) / (Math.min(a, c) + 0.05); // контраст по WCAG + const L = lum(bg); // текст — тот из двух, что контрастнее фону + const fg = ratio(lum('__LIGHT__'), L) > ratio(lum('__DARK__'), L) ? '__LIGHT__' : '__DARK__'; const f = rgb(fg), k = rgb(bg); // dim — как у темы: 60% текста, 40% фона const dim = '#' + f.map((c, i) => Math.round(c * 0.6 + k[i] * 0.4).toString(16).padStart(2, '0')).join(''); return {bg, fg, dim}; diff --git a/openspec/changes/headless-panel/design.md b/openspec/changes/headless-panel/design.md index 6d89bf0..2ae8455 100644 --- a/openspec/changes/headless-panel/design.md +++ b/openspec/changes/headless-panel/design.md @@ -145,8 +145,10 @@ tmux; фон выставлен под тему Termius и держится ме - 2026-10-18 (план) — Копия скрипта для службы — `~/.local/share/claude-agents/claude_agents.py`, отдельно от копии в AutoLaunch. Обоснование: служба не зависит от каталога iTerm2. Цена ошибки: два места при ручном обновлении (`install.sh` обновляет оба). -- 2026-10-18 (план) — Текст на светлом фоне `#3b4a50`, порог — относительная яркость фона по WCAG - > 0.4; на тёмном фоне текст `#aacace` (как в профиле). Цена ошибки: подобрать другие значения. +- 2026-10-18 (план) — Текст — `#3b4a50` (светлый фон) или `#aacace` (как в профиле, тёмный фон); + выбирается тот, у которого выше коэффициент контраста WCAG `(Lmax+0.05)/(Lmin+0.05)` с фоном + (было: порог яркости > 0.4, на средних тонах давал ~1.5:1 — находка I-1). Цена ошибки: + на отдельных средних фонах контраст всё равно не дотянет до 4.5:1; подобрать другие цвета текста. - 2026-10-18 (план) — Область нажатия ⚙, образцов и кнопок — не меньше 36×36 CSS px. Цена ошибки: поправить размер после приёмки на iPad. - 2026-10-18 (план) — В этой ветке заодно закрываются O-1 (устаревший docstring) и O-3 diff --git a/tests/test_appearance.py b/tests/test_appearance.py index 0f2bfd1..5c308e5 100644 --- a/tests/test_appearance.py +++ b/tests/test_appearance.py @@ -56,6 +56,13 @@ class ClientColors(unittest.TestCase): {"bg": "#002b36", "fg": "#aacace", "dim": "#668a91"}, ]) + def test_text_colour_by_contrast(self): + cases = {"#a0a0a0": "#3b4a50", "#999999": "#3b4a50", "#6699cc": "#3b4a50", "#333333": "#aacace"} + js = ca.CLIENT_COLORS_JS + ( + "\nconsole.log(JSON.stringify(%s.map(c => panelColors(c).fg)))" % json.dumps(list(cases))) + out = subprocess.run(["node", "-e", js], capture_output=True, text=True, check=True).stdout + self.assertEqual(json.loads(out), list(cases.values())) + if __name__ == "__main__": unittest.main()