DS3 (design): the color contract — one meaning, enforced by tests
Signal-green #00D4A0 now means exactly ONE thing (edge/active/A-tier/CTA), locked by tests that fail on violation. Part 1 of DESIGN-SPEC v2. - web/src/lib/colorContract.js — pure CommonJS helpers: edgeColor(value) colors edge/CLV/delta by SIGN (neg=var(--miss), pos=var(--g-a), 0=neutral); gradeTierColor() (A/A+ green, B blue, C amber, D/F red, in lockstep with vyndrTokens.gradeColor); gradeGlows() (A/A+ only); deltaE()/isSignalGreen() CIE76 gate so no archetype hue dilutes the signal. - GradeResultCard: edge confidence-strip + EDGE row route through edgeColor (a -33.3% edge was rendering GREEN — audit #3); grade-hero glow gated to A/A+ via gradeGlows (a glowing C devalued the cue); VYNDR INTELLIGENCE panel de-flooded (neutral border, Form/Rest neutral not green — #15). - LiveHeroProp: negative edge now muted red, not neutral (sign completeness). - Archetype dedup off signal-green (both archetypes.js + archetypeService.js, kept matched): DUAL THREAT/MOTOR #00D4A0, MIRROR #34D399, ARTILLERY/RANGE/ GHOST/BLADE #2DD4BF, BRUSH #3DDC84 shifted to distinct non-green hues (all ΔE>=44 from #00D4A0). Within-sport uniqueness preserved. - tests/unit/colorContract.test.js — 21 tests: helper units + source-grep violation locks + archetype-green-dedup. QA.20-22 kept green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
/* ============================================================
|
||||
VYNDR 2.0 — THE COLOR CONTRACT (DS3, DESIGN-SPEC Part 1).
|
||||
Plain CommonJS so .tsx components import it AND the Jest suite
|
||||
requires it directly (same pattern as vyndrTokens.js / archetypes.js).
|
||||
|
||||
ONE law, enforced in one place, tested in colorContract.test.js:
|
||||
- Signal-green (#00D4A0 / var(--g-a)) means exactly ONE thing:
|
||||
edge / active / A-tier / primary CTA. Never decorative, never an
|
||||
archetype hue, never a green rendered on a NEGATIVE number.
|
||||
- Edge / CLV / delta is colored by SIGN: positive = signal-green,
|
||||
negative = muted red (var(--miss)), zero = neutral (no edge).
|
||||
- Grades are colored by TIER (A/A+ green, B blue, C amber, D/F red).
|
||||
- GLOW is the scarcest cue in the system: A / A+ ONLY.
|
||||
============================================================ */
|
||||
|
||||
const SIGNAL_GREEN = '#00D4A0';
|
||||
|
||||
/* Archetype hues must clear this CIE76 ΔE from SIGNAL_GREEN so that
|
||||
#00D4A0 stays uniquely "edge" and is never diluted by an archetype
|
||||
chip. 20 sits above the ~10 "clearly a different color" boundary. */
|
||||
const MIN_ARCHETYPE_DELTA_E = 20;
|
||||
|
||||
/* Sign → token. Positive edge is the ONE green meaning; negative is
|
||||
muted red; a zero (or non-numeric) edge is no edge → neutral. */
|
||||
const EDGE_POSITIVE = 'var(--g-a)';
|
||||
const EDGE_NEGATIVE = 'var(--miss)';
|
||||
const EDGE_NEUTRAL = 'var(--text-2)';
|
||||
|
||||
/**
|
||||
* edgeColor(value) — the single source of truth for coloring any
|
||||
* edge / CLV / delta figure by SIGN. A -33.3% edge can NEVER render
|
||||
* green (audit #3). Accepts number or numeric string; null/NaN/0 → neutral.
|
||||
*/
|
||||
function edgeColor(value) {
|
||||
const n = typeof value === 'number' ? value : parseFloat(value);
|
||||
if (!Number.isFinite(n) || n === 0) return EDGE_NEUTRAL;
|
||||
return n > 0 ? EDGE_POSITIVE : EDGE_NEGATIVE;
|
||||
}
|
||||
|
||||
/* Grade → tier color. Mirrors vyndrTokens.gradeColor for the shared
|
||||
keys (a test locks the two in sync) and extends F → red. */
|
||||
const TIER_COLORS = {
|
||||
'A+': 'var(--g-ap)', A: 'var(--g-a)', 'A-': 'var(--g-a)',
|
||||
'B+': 'var(--g-b)', B: 'var(--g-b)', 'B-': 'var(--g-b)',
|
||||
C: 'var(--g-c)', D: 'var(--g-d)', F: 'var(--g-d)',
|
||||
};
|
||||
|
||||
function normalizeGrade(grade) {
|
||||
return (grade == null ? '' : String(grade)).trim().toUpperCase();
|
||||
}
|
||||
|
||||
/** gradeTierColor(grade) — A/A+ green, B blue, C amber, D/F red. */
|
||||
function gradeTierColor(grade) {
|
||||
return TIER_COLORS[normalizeGrade(grade)] || 'var(--text-0)';
|
||||
}
|
||||
|
||||
/**
|
||||
* gradeGlows(grade) — GLOW = A / A+ ONLY (audit #3: a glowing C
|
||||
* devalues the cue). A- is NOT a glow tier. Every phosphor/textShadow/
|
||||
* boxShadow glow on a grade element must gate through this.
|
||||
*/
|
||||
function gradeGlows(grade) {
|
||||
const g = normalizeGrade(grade);
|
||||
return g === 'A+' || g === 'A';
|
||||
}
|
||||
|
||||
/* ---- perceptual color distance (CIE76 in CIE-Lab) ---------------- */
|
||||
function hexToRgb(hex) {
|
||||
const h = String(hex).replace('#', '');
|
||||
return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
|
||||
}
|
||||
function srgbToLinear(c) {
|
||||
c /= 255;
|
||||
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
||||
}
|
||||
function hexToLab(hex) {
|
||||
const [r, g, b] = hexToRgb(hex).map(srgbToLinear);
|
||||
let x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047;
|
||||
let y = r * 0.2126 + g * 0.7152 + b * 0.0722;
|
||||
let z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883;
|
||||
const f = (t) => (t > 0.008856 ? Math.cbrt(t) : 7.787 * t + 16 / 116);
|
||||
const fx = f(x), fy = f(y), fz = f(z);
|
||||
return [116 * fy - 16, 500 * (fx - fy), 200 * (fy - fz)];
|
||||
}
|
||||
/** deltaE(a, b) — CIE76 perceptual distance between two hex colors. */
|
||||
function deltaE(a, b) {
|
||||
const A = hexToLab(a), B = hexToLab(b);
|
||||
return Math.sqrt((A[0] - B[0]) ** 2 + (A[1] - B[1]) ** 2 + (A[2] - B[2]) ** 2);
|
||||
}
|
||||
|
||||
/** isSignalGreen(hex) — true when a hue is close enough to the signal
|
||||
* green that it would dilute the "edge" meaning (archetype dedup gate). */
|
||||
function isSignalGreen(hex) {
|
||||
return deltaE(hex, SIGNAL_GREEN) < MIN_ARCHETYPE_DELTA_E;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
SIGNAL_GREEN,
|
||||
MIN_ARCHETYPE_DELTA_E,
|
||||
EDGE_POSITIVE,
|
||||
EDGE_NEGATIVE,
|
||||
EDGE_NEUTRAL,
|
||||
TIER_COLORS,
|
||||
edgeColor,
|
||||
gradeTierColor,
|
||||
gradeGlows,
|
||||
deltaE,
|
||||
isSignalGreen,
|
||||
};
|
||||
Reference in New Issue
Block a user