Files
portal/liderra_v8_handoff/scripts/palette_v7.py
T
Дмитрий 887abf444e rebrand(v8.5→Лидерра): дизайн-handoff Платона v8 Forest + Лидпоток→Лидерра
Получен handoff-пакет liderra_v8_handoff/ от дизайнера Платона
(kpd9363@gmail.com) от 07.05.2026 — v8 Forest. Заказчик 08.05 решил
применить только в части дизайна, имени, логотипа. Функционал, состав
страниц и правила (CTO-11, click-wrap, SSO break-glass, 14 статусов
воронки) — без изменений (источник — ТЗ v8.5/schema v8.5).

Что сделано:

- Массовая замена Лидпоток→Лидерра (с учётом падежей: Лидерры/Лидерре)
  в 33 файлах (449 вхождений) — все .md/.sql/.json/.toml/.yml/.txt/.html,
  кроме исторических упоминаний внутри liderra_v8_handoff/
- Удалён docs/brandbook.md v1.1 — заменён на BRANDBOOK_v2.md из handoff
- Скопированы 13 концептов liderra_v8_handoff/concepts/v8_*.html в
  web/v8/. Удалены старые web/01-login.html, 02-dashboard.html,
  03-deals.html, index.html (палитра v1.1 deprecated)
- CLAUDE.md v1.0→v1.1: §0 (BRANDBOOK_v2 + DEVELOPER_HANDOFF в источниках),
  §2 (палитра Forest, Inter+JBM, Lucide), §5 п.6 (anti-pattern Inter
  снят — в Forest Inter наш основной шрифт), §6 (13 концептов в web/v8/)
- Реестр Открытые_вопросы_v8_3.md v1.12→v1.13: добавлена запись о
  ребрендинге + 4 точечных расхождений handoff vs ТЗ (статусы воронки,
  click-wrap чекбоксы, SSO fallback, axe violations)
- package.json/package-lock.json: name lidpotok→liderra

4 расхождения handoff vs ТЗ (НЕ применены, источник истины — ТЗ/schema):

1. 14 «обобщённых» статусов в BRANDBOOK_v2 §3.6 ≠ 14 slug'ов в
   schema.sql:2076 (совпадает 2 из 14: «Переговоры», «Оплачено»).
   Источник — schema/ТЗ §6.4 (реселлерская модель из аудита crm.bp-gr.ru,
   6 системных + 8 настраиваемых статусов).
2. 3-й click-wrap в v8_login.html («маркетинг-опционально») ≠ ТЗ §1.5/§4.1
   («согласие на ПДн», обязательное, OPEN-Ж-3).
3. SSO в v8_admin.html («локальный 2FA fallback») ≠ ТЗ OPEN-И-13
   (break-glass super_admin, локальный 2FA выключен).
4. Заявление «axe-core 4.10.2 — 0 violations» в README handoff — локально
   Pa11y 9.1.1 + axe нашёл 81 violation на 10/13 HTML (преимущественно
   color-contrast на декоративных separator'ах с --ink-disabled).
   Чисто: settings/errors/palette_options.

Что НЕ включено в коммит:
- лендинг/TZ_landing_v1_0.md — untracked, не моя работа в этой сессии
- .tmp/ — gitignored

Что осталось (для следующих сессий):
- Возможное переименование GitHub-репо CoralMinister/lidpotok → liderra
  (отдельное решение заказчика)
- Опционально: обратная связь Платону по 4 расхождениям handoff vs ТЗ

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 07:11:58 +03:00

88 lines
3.7 KiB
Python

"""v7 palette — cool-tech light theme. Proves WCAG AA before HTML write."""
import colour
import numpy as np
def hex_rgb(h):
h = h.lstrip("#")
return np.array([int(h[i:i+2], 16)/255 for i in (0,2,4)])
def srgb_hex(rgb):
rgb = np.clip(rgb, 0, 1)
return "#" + "".join(f"{round(c*255):02X}" for c in rgb)
def oklch_to_hex(L, C, H):
H = np.deg2rad(H)
Lab = np.array([L, C*np.cos(H), C*np.sin(H)])
XYZ = colour.Oklab_to_XYZ(Lab)
rgb_lin = colour.XYZ_to_sRGB(XYZ, apply_cctf_encoding=False)
rgb = colour.cctf_encoding(np.clip(rgb_lin, 0, 1), function='sRGB')
return srgb_hex(rgb), rgb
def lum(rgb):
rgb = np.clip(rgb, 0, 1)
rl = np.where(rgb <= 0.03928, rgb/12.92, ((rgb+0.055)/1.055)**2.4)
return float(0.2126*rl[0] + 0.7152*rl[1] + 0.0722*rl[2])
def cr(a, b):
L1, L2 = lum(a), lum(b)
if L1 < L2: L1, L2 = L2, L1
return (L1 + 0.05) / (L2 + 0.05)
# Cool-tech tokens — все вокруг hue=240 (нейтральный синеватый), чтобы поверхность читалась
# как "лабораторная" а не "тёплая бумага". Brand accent — Teal #0F6E56 из брендбука,
# его НЕ трогаем.
print("=" * 80)
print("v7 cool-tech tokens (Page surface — neutral cool, hue=240)")
print("=" * 80)
# Светлые поверхности
bg_hex, bg_rgb = oklch_to_hex(0.985, 0.003, 240) # page bg
surface_hex, surface_rgb = oklch_to_hex(1.000, 0.000, 240) # raised — pure white
sunken_hex, sunken_rgb = oklch_to_hex(0.970, 0.004, 240) # sunken
hairline_hex, hairline_rgb = oklch_to_hex(0.910, 0.005, 240) # hairline (UI border, not text)
# Текст
ink_hex, ink_rgb = oklch_to_hex(0.180, 0.018, 240) # primary text
ink2_hex, ink2_rgb = oklch_to_hex(0.350, 0.014, 240) # secondary
ink3_hex, ink3_rgb = oklch_to_hex(0.510, 0.012, 240) # muted (must AA on bg)
inkd_hex, inkd_rgb = oklch_to_hex(0.620, 0.008, 240) # disabled (3:1+)
# Brand accent (Teal #0F6E56) — НЕ пересчитываем, фиксируем из брендбука
accent_rgb = hex_rgb('#0F6E56')
palette = [
('bg', bg_hex, bg_rgb),
('surface', surface_hex, surface_rgb),
('sunken', sunken_hex, sunken_rgb),
('hairline', hairline_hex, hairline_rgb),
('ink', ink_hex, ink_rgb),
('ink-2', ink2_hex, ink2_rgb),
('ink-3', ink3_hex, ink3_rgb),
('ink-disabled',inkd_hex, inkd_rgb),
('accent (teal)','#0F6E56', accent_rgb),
]
for name, h, _ in palette:
print(f" {name:<14} {h}")
print()
print("WCAG verifications — text colour on bg/surface/sunken")
print("=" * 80)
checks = [
('ink / bg', ink_rgb, bg_rgb, 4.5),
('ink / surface', ink_rgb, surface_rgb, 4.5),
('ink / sunken', ink_rgb, sunken_rgb, 4.5),
('ink-2 / bg', ink2_rgb, bg_rgb, 4.5),
('ink-2 / sunken', ink2_rgb, sunken_rgb, 4.5),
('ink-3 / bg', ink3_rgb, bg_rgb, 4.5),
('ink-3 / surface', ink3_rgb, surface_rgb, 4.5),
('ink-3 / sunken', ink3_rgb, sunken_rgb, 4.5),
('ink-disabled / bg', inkd_rgb, bg_rgb, 3.0), # disabled — 3:1 OK
('accent / bg', accent_rgb, bg_rgb, 4.5),
('accent / surface',accent_rgb, surface_rgb, 4.5),
('white-on-accent (CTA text)', np.array([1,1,1]), accent_rgb, 4.5),
('white-on-ink (active nav)', np.array([1,1,1]), ink_rgb, 4.5),
('hairline / bg (3:1 UI)', hairline_rgb, bg_rgb, 3.0),
]
for name, a, b, target in checks:
r = cr(a, b)
flag = '' if r >= target else 'FAIL'
target_str = f"{target}"
print(f" {name:<35} {r:5.2f}:1 {target_str:>5} {flag}")