""" 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()