Files
portal/liderra_v8_handoff/scripts/palette_options.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

152 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
5 colour-scheme variants for v8 — bigger sidebar/main contrast.
Constraint: brand accent Teal #0F6E56 не оспариваем (брендбук v1.1).
Each variant fully WCAG-AA verified: ink/bg, ink/surface, ink/sidebar (text on
sidebar bg), accent/bg, white/sidebar-active, sidebar/bg (≥3:1 чтобы visually
"выделить левую часть").
"""
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(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)
return colour.cctf_encoding(np.clip(rgb_lin, 0, 1), function='sRGB')
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)
WHITE = np.array([1.0,1.0,1.0])
ACCENT = hex_rgb('#0F6E56') # Teal — брендбук v1.1, не оспаривается
# Bright-Teal для icon-on-dark контекста (Teal слишком тёмный для тёмного sidebar)
ACCENT_BRIGHT = oklch(0.75, 0.13, 175)
print(f"# accent-bright (icon on dark sidebar): {srgb_hex(ACCENT_BRIGHT)}")
print()
# 5 variants — each defined by (sidebar bg, sidebar text, sidebar active fill,
# page bg, surface, ink, ink-3, optional secondary accent)
variants = []
# ───── Вариант А — Graphite Sidebar (Linear/Vercel-grade)
variants.append({
'name': 'А · Graphite Sidebar',
'desc': 'Тёмный графитовый sidebar + холодный near-white main. Самый «технологичный» из всех — линия Linear/Vercel. Контраст левая/правая = максимальный.',
'sidebar': oklch(0.205, 0.012, 240), # near-black cool
'sidebar_text': oklch(0.78, 0.005, 240),
'sidebar_act': oklch(0.30, 0.015, 240),
'bg': oklch(0.965, 0.005, 240), # cooler than v7
'surface': WHITE,
'sunken': oklch(0.945, 0.006, 240),
'hairline': oklch(0.88, 0.006, 240),
'ink': oklch(0.18, 0.018, 240),
'ink_3': oklch(0.50, 0.012, 240),
})
# ───── Вариант Б — Forest Sidebar (бренд-родной)
variants.append({
'name': 'Б · Forest Sidebar',
'desc': 'Глубокий тёмно-тиловый sidebar (родственник брендового Teal) + тёплый ivory main. Брендовая связность, корпоративная фундаментальность.',
'sidebar': oklch(0.22, 0.040, 175), # deep teal-noir
'sidebar_text': oklch(0.80, 0.020, 175),
'sidebar_act': oklch(0.31, 0.045, 175),
'bg': oklch(0.965, 0.010, 90), # warm ivory
'surface': oklch(0.995, 0.005, 90),
'sunken': oklch(0.945, 0.012, 90),
'hairline': oklch(0.875, 0.012, 85),
'ink': oklch(0.18, 0.020, 230),
'ink_3': oklch(0.50, 0.012, 90),
})
# ───── Вариант В — Slate-Blue Financial
variants.append({
'name': 'В · Slate-Blue Financial',
'desc': 'Sidebar — глубокий синий слейт, main — холодный mist. Stripe Atlas/Mercury вайб, финансовый инструмент.',
'sidebar': oklch(0.235, 0.030, 250), # deep navy slate
'sidebar_text': oklch(0.80, 0.015, 250),
'sidebar_act': oklch(0.34, 0.035, 250),
'bg': oklch(0.945, 0.010, 240), # cool mist
'surface': WHITE,
'sunken': oklch(0.92, 0.012, 240),
'hairline': oklch(0.86, 0.014, 240),
'ink': oklch(0.18, 0.020, 245),
'ink_3': oklch(0.50, 0.014, 245),
})
# ───── Вариант Г — Warm Champagne
variants.append({
'name': 'Г · Warm Champagne',
'desc': 'Sidebar — насыщенный кремово-бежевый, main — pure white. Тёплый «кабинет», Notion/Things с строгостью.',
'sidebar': oklch(0.92, 0.025, 80), # rich champagne
'sidebar_text': oklch(0.32, 0.015, 80),
'sidebar_act': oklch(0.18, 0.020, 240), # ink fill
'bg': WHITE,
'surface': oklch(0.985, 0.005, 80),
'sunken': oklch(0.94, 0.018, 80),
'hairline': oklch(0.86, 0.022, 80),
'ink': oklch(0.18, 0.020, 240),
'ink_3': oklch(0.48, 0.012, 80),
})
# ───── Вариант Д — Inverted Hero (cards float)
variants.append({
'name': 'Д · Inverted Hero',
'desc': 'Sidebar — pure white, main — насыщенный cool grey. Карточки «плавают» над полотном, классика премиум B2B (Stripe Dashboard).',
'sidebar': WHITE,
'sidebar_text': oklch(0.32, 0.014, 240),
'sidebar_act': oklch(0.18, 0.020, 240), # ink fill
'bg': oklch(0.91, 0.012, 240), # noticeably cool grey
'surface': WHITE,
'sunken': oklch(0.88, 0.013, 240),
'hairline': oklch(0.82, 0.015, 240),
'ink': oklch(0.18, 0.020, 245),
'ink_3': oklch(0.48, 0.013, 245),
})
# Compute & print
for v in variants:
print("=" * 84)
print(v['name'])
print(v['desc'])
print("-" * 84)
print(f" sidebar {srgb_hex(v['sidebar'])}")
print(f" sidebar text {srgb_hex(v['sidebar_text'])}")
print(f" sidebar act {srgb_hex(v['sidebar_act'])}")
print(f" bg {srgb_hex(v['bg'])}")
print(f" surface {srgb_hex(v['surface'])}")
print(f" sunken {srgb_hex(v['sunken'])}")
print(f" hairline {srgb_hex(v['hairline'])}")
print(f" ink {srgb_hex(v['ink'])}")
print(f" ink-3 {srgb_hex(v['ink_3'])}")
print(f" accent (teal, fixed) #0F6E56")
print()
print(" WCAG checks:")
pairs = [
('ink / bg', v['ink'], v['bg'], 4.5),
('ink / surface', v['ink'], v['surface'], 4.5),
('ink-3 / bg', v['ink_3'], v['bg'], 4.5),
('sidebar text / sidebar', v['sidebar_text'], v['sidebar'], 4.5),
('white / sidebar-active', WHITE, v['sidebar_act'], 4.5),
('accent / bg', ACCENT, v['bg'], 4.5),
('accent-bright / sidebar (icon)', ACCENT_BRIGHT, v['sidebar'], 3.0),
('SIDEBAR vs BG (layout div)', v['sidebar'], v['bg'], 3.0),
]
for name, a, b, target in pairs:
r = cr(a, b)
flag = '' if r >= target else 'FAIL'
print(f" {name:<32} {r:6.2f}:1 ≥{target} {flag}")
print()