code wiki / _hdl_build / nx_brand_guard.nx

nx_brand_guard.nx source

↩ module page · 128 lines · 6154 B

1// nx_brand_guard.nx -- R2 brand-consistency ENFORCEMENT, tied to the design-token SSOT. Three checks, all no-float: 2// (1) WCAG 2.x contrast ratio between two token colors -- accessibility-as-brand 3// (2) spacing-grid conformance (space tokens are multiples of a base unit) -- visual rhythm 4// (3) modular type-scale conformance (headings descend within a sane ratio band) -- typographic consistency 5// REUSE (audited, not reinvented): nx_css_color_decode (hex->rgb), nx_brand_tokens (bt_lookup), and fx.nx's 6// ACCURATE Q16.16 fx_pow for the sRGB gamma. NOTE: deliberately NOT nx_aces' pow24 -- that empirical fit carries 7// ~30% error at mid-tones and would misclassify contrast near the 4.5 threshold (hand-verified: #767676/#fff -> 8// 3.6 wrong vs 4.54 true). fx_pow is exp2/log2-based and matches the WCAG reference (validated in the gate, no 9// overclaim). CONSTANTS are PUBLISHED STANDARDS: sRGB OETF (IEC 61966-2-1) + Rec.709 luma (ITU-R BT.709). Library. 10// 100% sovereign (nx_cc->nxasm, no gcc/JS). license_tier: ORIGINAL 11import "nx_syscalls.nx" 12import "nx_css_color_decode.nx" 13import "nx_brand_tokens.nx" 14import "fx.nx" 15 16const BG_005: i64 = 3277 // 0.05 * 65536 (WCAG flare term) 17const BG_THR: i64 = 2651 // 0.04045 * 65536 (sRGB piecewise threshold) 18const BG_1292: i64 = 846746 // 12.92 * 65536 19const BG_1055: i64 = 69141 // 1.055 * 65536 20const BG_0055: i64 = 3604 // 0.055 * 65536 21const BG_24: i64 = 157286 // 2.4 * 65536 (sRGB gamma exponent) 22const BG_LR: i64 = 13934 // 0.2126 * 65536 Rec.709 luma R 23const BG_LG: i64 = 46870 // 0.7152 * 65536 Rec.709 luma G 24const BG_LB: i64 = 4732 // 0.0722 * 65536 Rec.709 luma B 25 26// one 8-bit channel (0..255) -> Q16.16 LINEAR via the sRGB OETF inverse (accurate fx_pow gamma). 27func bg_chan_lin(c: i64) -> i64 { 28 var cc: i64 = c 29 if cc < 0 { cc = 0 } 30 if cc > 255 { cc = 255 } 31 let x: i64 = (cc * FX_ONE) / 255 32 if x < BG_THR { return fx_div(x, BG_1292) } 33 let t: i64 = fx_div(x + BG_0055, BG_1055) 34 return fx_pow(t, BG_24) 35} 36// WCAG relative luminance (Q16.16) of an (r,g,b). 37func bg_rel_lum(r: i64, g: i64, b: i64) -> i64 { 38 return fx_mul(BG_LR, bg_chan_lin(r)) + fx_mul(BG_LG, bg_chan_lin(g)) + fx_mul(BG_LB, bg_chan_lin(b)) 39} 40// WCAG contrast ratio x100 between two colors (450 == 4.5:1). Symmetric (lighter/darker auto-ordered). 41func bg_contrast_x100(r1: i64, g1: i64, b1: i64, r2: i64, g2: i64, b2: i64) -> i64 { 42 let l1: i64 = bg_rel_lum(r1, g1, b1) 43 let l2: i64 = bg_rel_lum(r2, g2, b2) 44 var hi: i64 = l1 45 var lo: i64 = l2 46 if l2 > l1 { hi = l2; lo = l1 } 47 return ((hi + BG_005) * 100) / (lo + BG_005) 48} 49// decode a "#rrggbb" / "#rgb" token value into a CssColor. returns 1 ok, 0 fail. 50func bg_decode_hex(h: *u8, out: *CssColor) -> i64 { 51 var n: i64 = 0 52 while h[n] != (0 as u8) { n = n + 1 } 53 if n < 4 { return 0 } 54 if h[0] != (35 as u8) { return 0 } // '#' 55 return nx_css_color_decode(h, 1, n - 1, out) 56} 57// contrast x100 between two hex strings, or -1 if either fails to parse. 58func bg_contrast_hex_x100(h1: *u8, h2: *u8) -> i64 { 59 let c1: *CssColor = sys_mmap(32) as *CssColor 60 let c2: *CssColor = sys_mmap(32) as *CssColor 61 if bg_decode_hex(h1, c1) == 0 { return 0 - 1 } 62 if bg_decode_hex(h2, c2) == 0 { return 0 - 1 } 63 return bg_contrast_x100(c1.r, c1.g, c1.b, c2.r, c2.g, c2.b) 64} 65// resolve two brand tokens (group/name) and return their contrast x100 (or -1 if missing/unparseable). 66func bg_pair_contrast(brand: *u8, bn: i64, g1: *u8, n1: *u8, g2: *u8, n2: *u8) -> i64 { 67 let h1: *u8 = sys_mmap(64) 68 let h2: *u8 = sys_mmap(64) 69 if bt_lookup(brand, bn, g1, n1, h1, 64) < 0 { return 0 - 1 } 70 if bt_lookup(brand, bn, g2, n2, h2, 64) < 0 { return 0 - 1 } 71 return bg_contrast_hex_x100(h1, h2) 72} 73 74// ---- numeric token parse (spacing + type-scale) ---- 75// leading number of "16px"/"2rem"/"1.4rem" -> milli-units (16000 / 2000 / 1400). -1 if no leading digit. 76func bg_num_milli(s: *u8) -> i64 { 77 var i: i64 = 0 78 var ip: i64 = 0 79 var got: i64 = 0 80 var go: i64 = 1 81 while go == 1 { 82 let c: i64 = s[i] as i64 83 if c >= 48 { if c <= 57 { ip = ip*10 + (c-48); got = 1; i = i+1 } else { go = 0 } } else { go = 0 } 84 } 85 if got == 0 { return 0 - 1 } 86 var milli: i64 = ip * 1000 87 if s[i] == (46 as u8) { // '.' 88 i = i + 1 89 var den: i64 = 100 90 var cnt: i64 = 0 91 var gf: i64 = 1 92 while gf == 1 { 93 if cnt >= 3 { gf = 0 } 94 else { 95 let c: i64 = s[i] as i64 96 if c >= 48 { if c <= 57 { milli = milli + (c-48)*den; den = den/10; i = i+1; cnt = cnt+1 } else { gf = 0 } } else { gf = 0 } 97 } 98 } 99 } 100 return milli 101} 102// spacing-grid: is a space token a whole multiple of `unit` px? 1 ok, 0 violate, -1 missing/unparseable. 103func bg_space_grid_ok(brand: *u8, bn: i64, name: *u8, unit: i64) -> i64 { 104 let v: *u8 = sys_mmap(64) 105 if bt_lookup(brand, bn, "space" as *u8, name, v, 64) < 0 { return 0 - 1 } 106 let m: i64 = bg_num_milli(v) 107 if m < 0 { return 0 - 1 } 108 if (m % 1000) != 0 { return 0 } // fractional px -> off-grid 109 let px: i64 = m / 1000 110 if (px % unit) == 0 { return 1 } 111 return 0 112} 113// modular type-scale: font|h1 must DESCEND to font|h2 within ratio band [110,220] (x100). 1 ok, 0 violate, -1 missing. 114func bg_type_scale_ok(brand: *u8, bn: i64) -> i64 { 115 let a: *u8 = sys_mmap(64) 116 let b: *u8 = sys_mmap(64) 117 if bt_lookup(brand, bn, "font" as *u8, "h1" as *u8, a, 64) < 0 { return 0 - 1 } 118 if bt_lookup(brand, bn, "font" as *u8, "h2" as *u8, b, 64) < 0 { return 0 - 1 } 119 let m1: i64 = bg_num_milli(a) 120 let m2: i64 = bg_num_milli(b) 121 if m1 < 0 { return 0 - 1 } 122 if m2 < 0 { return 0 - 1 } 123 if m1 <= m2 { return 0 } // not descending 124 let ratio: i64 = (m1 * 100) / m2 125 if ratio < 110 { return 0 } // too tight to read as a hierarchy 126 if ratio > 220 { return 0 } // too loud / inconsistent 127 return 1 128}