|
|
|
|
|
/**
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
* Contrast audit — the measurement half of the "tonal-ramp contrast" initiative
|
|
|
|
|
|
* (docs/next-features.md §1). Measures whether the framework's slot-pair
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
* contrast PROMISES actually hold, across all 33 donor scales × 2 modes, and
|
|
|
|
|
|
* prints the drift.
|
|
|
|
|
|
*
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
* It consumes the RATIFIED pair table as data (`$color` → `CONTRAST_PAIRS`,
|
|
|
|
|
|
* doctrine `theming/reference.md §40`), so the audit and the future
|
|
|
|
|
|
* by-construction solver measure against ONE source. It runs over TWO banks:
|
|
|
|
|
|
* 1. the static base (ground-truth — the hard `text-strong·12` gate must hold),
|
|
|
|
|
|
* 2. morph-GENERATED output (the Stage-2 regression bank — the gap the solver
|
|
|
|
|
|
* closes).
|
|
|
|
|
|
*
|
|
|
|
|
|
* This is measurement only — it changes no output.
|
|
|
|
|
|
*
|
|
|
|
|
|
* node --import tsx/esm scripts/contrast-audit.ts
|
|
|
|
|
|
*/
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
import {
|
|
|
|
|
|
apcaLc,
|
|
|
|
|
|
CONTRAST_PAIRS,
|
|
|
|
|
|
generateScale,
|
|
|
|
|
|
oklchToGammaRgb,
|
|
|
|
|
|
oklchToHex,
|
|
|
|
|
|
pickNearestTemplate,
|
|
|
|
|
|
safeParseColor,
|
|
|
|
|
|
scaleToTemplate,
|
|
|
|
|
|
SECONDARY_TEXT_APCA_FLOOR,
|
|
|
|
|
|
wcagContrastRatio,
|
|
|
|
|
|
type ContrastPair,
|
|
|
|
|
|
type ScaleSteps
|
|
|
|
|
|
} from '$color';
|
|
|
|
|
|
import { PALETTE_SCALES } from '../src/uix/eidos/lib/types.ts';
|
|
|
|
|
|
import {
|
|
|
|
|
|
THEME_BASE_DARK_COLOR_SCALES,
|
|
|
|
|
|
THEME_BASE_LIGHT_COLOR_SCALES
|
|
|
|
|
|
} from '../src/uix/eidos/lib/themes/base.ts';
|
|
|
|
|
|
|
|
|
|
|
|
type StepMap = Record<string | number, string | undefined>;
|
|
|
|
|
|
|
|
|
|
|
|
const f2 = (n: number) => n.toFixed(2);
|
|
|
|
|
|
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
function measure(inkHex?: string, bgHex?: string): { wcag: number; apca: number } | null {
|
|
|
|
|
|
if (!inkHex || !bgHex) return null;
|
|
|
|
|
|
const ink = safeParseColor(inkHex);
|
|
|
|
|
|
const bg = safeParseColor(bgHex);
|
|
|
|
|
|
if (!ink || !bg) return null;
|
|
|
|
|
|
const inkRgb = oklchToGammaRgb(ink);
|
|
|
|
|
|
const bgRgb = oklchToGammaRgb(bg);
|
|
|
|
|
|
return { wcag: wcagContrastRatio(inkRgb, bgRgb), apca: Math.abs(apcaLc(inkRgb, bgRgb)) };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const MODES: readonly [string, Record<string, StepMap>][] = [
|
|
|
|
|
|
['light', THEME_BASE_LIGHT_COLOR_SCALES as unknown as Record<string, StepMap>],
|
|
|
|
|
|
['dark', THEME_BASE_DARK_COLOR_SCALES as unknown as Record<string, StepMap>]
|
|
|
|
|
|
];
|
|
|
|
|
|
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
// ── Pair measurement, per the ratified §40 contract ─────────────────────────
|
|
|
|
|
|
interface PairResult {
|
|
|
|
|
|
mode: string;
|
|
|
|
|
|
scale: string;
|
|
|
|
|
|
pair: ContrastPair;
|
|
|
|
|
|
wcag: number;
|
|
|
|
|
|
apca: number;
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
/** For `secondary` pairs: the APCA of the cap step (text-strong) over the same bg. */
|
|
|
|
|
|
capApca: number | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
function auditPairs(
|
|
|
|
|
|
scales: Record<string, StepMap>,
|
|
|
|
|
|
mode: string,
|
|
|
|
|
|
scaleNames: readonly string[]
|
|
|
|
|
|
): PairResult[] {
|
|
|
|
|
|
const out: PairResult[] = [];
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
for (const scale of scaleNames) {
|
|
|
|
|
|
const steps = scales[scale];
|
|
|
|
|
|
if (!steps) continue;
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
for (const pair of CONTRAST_PAIRS) {
|
|
|
|
|
|
const c = measure(steps[pair.ink], steps[pair.bg]);
|
|
|
|
|
|
if (!c) continue;
|
|
|
|
|
|
let capApca: number | null = null;
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
if (pair.policy.gate === 'secondary') {
|
|
|
|
|
|
const cc = measure(steps[pair.policy.apcaCapStep], steps[pair.bg]);
|
|
|
|
|
|
capApca = cc ? cc.apca : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
out.push({ mode, scale, pair, wcag: c.wcag, apca: c.apca, capApca });
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return out;
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Print the three §40 tiers for a bank; returns the count of HARD-gate failures. */
|
|
|
|
|
|
function summarize(results: PairResult[], title: string): number {
|
|
|
|
|
|
console.log(`\n${title}`);
|
|
|
|
|
|
const aa = results.filter((r) => r.pair.policy.gate === 'aa');
|
|
|
|
|
|
const sec = results.filter((r) => r.pair.policy.gate === 'secondary');
|
|
|
|
|
|
const dec = results.filter((r) => r.pair.policy.gate === 'exempt');
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
|
|
|
|
|
|
// Hard AA gate — text-strong·12 (the one ratified floor).
|
|
|
|
|
|
const aaFail = aa.filter((r) => r.pair.policy.gate === 'aa' && r.wcag < r.pair.policy.wcag);
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
if (aaFail.length === 0) {
|
|
|
|
|
|
const aaMin = aa.length ? Math.min(...aa.map((r) => r.wcag)) : NaN;
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
console.log(
|
|
|
|
|
|
` ✅ HARD · text-strong·12 ≥ 4.5:1 WCAG — ${aa.length}/${aa.length} clear (min ${f2(aaMin)}:1)`
|
|
|
|
|
|
);
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
} else {
|
|
|
|
|
|
console.log(` ⚠️ HARD · text-strong·12 ≥ 4.5:1 WCAG — ${aaFail.length}/${aa.length} FAIL:`);
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
for (const r of aaFail.sort((a, b) => a.wcag - b.wcag))
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
` ${r.mode.padEnd(5)} ${r.scale.padEnd(10)} ${r.pair.label.padEnd(28)} ${f2(r.wcag)}:1`
|
|
|
|
|
|
);
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Secondary tier — text·11 band (≈APCA 60; sub-4.5 by design → informational).
|
|
|
|
|
|
if (sec.length) {
|
|
|
|
|
|
const apcas = sec.map((r) => r.apca);
|
|
|
|
|
|
const below = sec.filter((r) => r.apca < SECONDARY_TEXT_APCA_FLOOR);
|
|
|
|
|
|
const overCap = sec.filter((r) => r.capApca != null && r.apca > r.capApca);
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
console.log(
|
|
|
|
|
|
` • SOFT · text·11 ≈APCA ${SECONDARY_TEXT_APCA_FLOOR} band — Lc ${f2(Math.min(...apcas))}–${f2(Math.max(...apcas))} · ${below.length}/${sec.length} below floor (by design) · ≤ text-strong: ${overCap.length === 0 ? '✅' : `⚠️ ${overCap.length} over cap`}`
|
|
|
|
|
|
);
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Decorative — border·7 (exempt): measured for context only.
|
|
|
|
|
|
if (dec.length) {
|
|
|
|
|
|
const w = dec.map((r) => r.wcag);
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
console.log(
|
|
|
|
|
|
` · EXEMPT· border·7 — WCAG ${f2(Math.min(...w))}–${f2(Math.max(...w))}:1 (decorative accent, not gated)`
|
|
|
|
|
|
);
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
}
|
|
|
|
|
|
return aaFail.length;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
// ── Bank 1: the static base (ground-truth) ──────────────────────────────────
|
|
|
|
|
|
const baseResults: PairResult[] = [];
|
|
|
|
|
|
for (const [mode, scales] of MODES) baseResults.push(...auditPairs(scales, mode, PALETTE_SCALES));
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
`\nContrast audit — §40 tonal-ramp contract (${PALETTE_SCALES.length} scales × 2 modes)`
|
|
|
|
|
|
);
|
|
|
|
|
|
const baseFails = summarize(baseResults, '── Bank 1 · static base (ground-truth)');
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
|
|
|
|
|
|
// ── Tier-1: the LOAD-BEARING borders (WCAG 1.4.11 — 3:1 required) ───────────
|
|
|
|
|
|
// The per-scale `border` (step 7) is a DECORATIVE accent (used only when a
|
|
|
|
|
|
// filled/colored element also carries its boundary via bg + content) → exempt.
|
|
|
|
|
|
// The borders that ARE the sole indicator of a control's boundary/state use
|
|
|
|
|
|
// STRONGER tokens; those are the real 3:1 promise. Role bindings in the base:
|
|
|
|
|
|
// neutral→gray, primary→purple, risk→orange.
|
|
|
|
|
|
function compositeSrgb(fgHex: string, alpha: number, bgHex: string): string | null {
|
|
|
|
|
|
const fg = safeParseColor(fgHex);
|
|
|
|
|
|
const bg = safeParseColor(bgHex);
|
|
|
|
|
|
if (!fg || !bg) return null;
|
|
|
|
|
|
// `oklchToGammaRgb` returns a normalized [0,1] `[r,g,b]` tuple — scale to [0,255].
|
|
|
|
|
|
const f = oklchToGammaRgb(fg);
|
|
|
|
|
|
const b = oklchToGammaRgb(bg);
|
|
|
|
|
|
const hex = (i: 0 | 1 | 2) =>
|
|
|
|
|
|
Math.max(0, Math.min(255, Math.round((f[i] * alpha + b[i] * (1 - alpha)) * 255)))
|
|
|
|
|
|
.toString(16)
|
|
|
|
|
|
.padStart(2, '0');
|
|
|
|
|
|
return `#${hex(0)}${hex(1)}${hex(2)}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Role bindings in the base theme: neutral→gray, primary→purple, risk→orange.
|
|
|
|
|
|
const NEUTRAL = 'gray';
|
|
|
|
|
|
const PRIMARY = 'purple';
|
|
|
|
|
|
const RISK = 'orange';
|
|
|
|
|
|
const FOCUS_ALPHA: Record<string, number> = { light: 0.48, dark: 0.52 };
|
|
|
|
|
|
const RING_ERROR_ALPHA: Record<string, number> = { light: 0.52, dark: 0.56 };
|
|
|
|
|
|
|
|
|
|
|
|
// The surfaces a control (and thus its border) sits on.
|
|
|
|
|
|
const SURFACES: readonly { id: string; step: number }[] = [
|
|
|
|
|
|
{ id: 'default', step: 1 },
|
|
|
|
|
|
{ id: 'raised', step: 2 },
|
|
|
|
|
|
{ id: 'muted', step: 3 }
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
interface BorderToken {
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
/** `true` = the sole indicator of a boundary/state → WCAG 1.4.11 wants 3:1. */
|
|
|
|
|
|
loadBearing: boolean;
|
|
|
|
|
|
/** Resolve the (already-composited) border color for a mode's scale map. */
|
|
|
|
|
|
color: (s: Record<string, StepMap>, mode: string, surfaceHex: string) => string | undefined;
|
|
|
|
|
|
}
|
|
|
|
|
|
const BORDER_TOKENS: readonly BorderToken[] = [
|
|
|
|
|
|
{ label: 'border-subtle (neutral·4)', loadBearing: false, color: (s) => s[NEUTRAL]?.[4] },
|
|
|
|
|
|
{
|
|
|
|
|
|
label: 'border-default (neutral·6) — resting',
|
|
|
|
|
|
loadBearing: false,
|
|
|
|
|
|
color: (s) => s[NEUTRAL]?.[6]
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
label: 'border-strong (neutral·8) — hover/ghost',
|
|
|
|
|
|
loadBearing: true,
|
|
|
|
|
|
color: (s) => s[NEUTRAL]?.[8]
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
label: 'focus ring (primary·8 @ ~50%)',
|
|
|
|
|
|
loadBearing: true,
|
|
|
|
|
|
color: (s, mode, surf) => {
|
|
|
|
|
|
const fg = s[PRIMARY]?.[8];
|
|
|
|
|
|
return fg ? (compositeSrgb(fg, FOCUS_ALPHA[mode], surf) ?? undefined) : undefined;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
label: 'focus ring-error (risk·8 @ ~54%)',
|
|
|
|
|
|
loadBearing: true,
|
|
|
|
|
|
color: (s, mode, surf) => {
|
|
|
|
|
|
const fg = s[RISK]?.[8];
|
|
|
|
|
|
return fg ? (compositeSrgb(fg, RING_ERROR_ALPHA[mode], surf) ?? undefined) : undefined;
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{ label: 'invalid border (risk·7)', loadBearing: true, color: (s) => s[RISK]?.[7] },
|
|
|
|
|
|
{ label: 'checked/selected (primary·9 solid)', loadBearing: true, color: (s) => s[PRIMARY]?.[9] }
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
interface T1Row {
|
|
|
|
|
|
mode: string;
|
|
|
|
|
|
surface: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
loadBearing: boolean;
|
|
|
|
|
|
wcag: number;
|
|
|
|
|
|
apca: number;
|
|
|
|
|
|
pass: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
const t1: T1Row[] = [];
|
|
|
|
|
|
for (const [mode, scales] of MODES) {
|
|
|
|
|
|
for (const surf of SURFACES) {
|
|
|
|
|
|
const surfaceHex = scales[NEUTRAL]?.[surf.step];
|
|
|
|
|
|
if (!surfaceHex) continue;
|
|
|
|
|
|
for (const bt of BORDER_TOKENS) {
|
|
|
|
|
|
const c = bt.color(scales, mode, surfaceHex);
|
|
|
|
|
|
if (!c) continue;
|
|
|
|
|
|
const m = measure(c, surfaceHex);
|
|
|
|
|
|
if (!m) continue;
|
|
|
|
|
|
t1.push({
|
|
|
|
|
|
mode,
|
|
|
|
|
|
surface: surf.id,
|
|
|
|
|
|
label: bt.label,
|
|
|
|
|
|
loadBearing: bt.loadBearing,
|
|
|
|
|
|
wcag: m.wcag,
|
|
|
|
|
|
apca: m.apca,
|
|
|
|
|
|
pass: m.wcag >= 3
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Semantic border matrix (the tokens that ARE used as borders) ────────────
|
|
|
|
|
|
// `L` = load-bearing (sole indicator → WCAG 1.4.11 wants 3:1); `·` = decorative
|
|
|
|
|
|
// (accompanied by a fill / other cue → exempt). Floor 3:1 for the L rows.
|
|
|
|
|
|
console.log('\nSemantic border matrix — WCAG:1 vs each surface (L = load-bearing/3:1-required):\n');
|
|
|
|
|
|
const surfIds = SURFACES.map((s) => s.id);
|
|
|
|
|
|
const header =
|
|
|
|
|
|
` ${'token'.padEnd(38)} ${'kind'.padEnd(4)} ` +
|
|
|
|
|
|
surfIds.map((s) => `${('' + s).padEnd(14)}`).join('');
|
|
|
|
|
|
for (const mode of ['light', 'dark']) {
|
|
|
|
|
|
console.log(`── ${mode} (surface: default·1 / raised·2 / muted·3)`);
|
|
|
|
|
|
console.log(header);
|
|
|
|
|
|
for (const bt of BORDER_TOKENS) {
|
|
|
|
|
|
const cells = surfIds.map((sid) => {
|
|
|
|
|
|
const r = t1.find((x) => x.mode === mode && x.surface === sid && x.label === bt.label);
|
|
|
|
|
|
if (!r) return ''.padEnd(14);
|
|
|
|
|
|
const mark = !r.loadBearing ? ' ' : r.pass ? '✅' : '⚠️';
|
|
|
|
|
|
return `${mark}${f2(r.wcag)}`.padEnd(14);
|
|
|
|
|
|
});
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
` ${bt.label.padEnd(38)} ${(bt.loadBearing ? 'L' : '·').padEnd(4)} ${cells.join('')}`
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
console.log('');
|
|
|
|
|
|
}
|
|
|
|
|
|
const lbFails = t1.filter((r) => r.loadBearing && !r.pass);
|
|
|
|
|
|
console.log(
|
|
|
|
|
|
lbFails.length === 0
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
? 'Load-bearing borders: ✅ all clear 3:1.'
|
|
|
|
|
|
: `Load-bearing 3:1 FAILURES: ${lbFails.length} → ${[...new Set(lbFails.map((r) => r.label))].join(' · ')}`
|
|
|
|
|
|
);
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
|
|
|
|
|
|
// ── Bank 2: morph-GENERATED output (the Stage-2 regression bank) ─────────────
|
|
|
|
|
|
// For every base scale, treat its solid (step 9) as a seed and MORPH-generate a
|
|
|
|
|
|
// fresh scale from the OTHER base scales as templates (the seed's own scale is
|
|
|
|
|
|
// excluded — else it lands on itself and the test trivially equals the base).
|
|
|
|
|
|
// This is morph-only (no solver yet). Finding: it already clears the HARD
|
|
|
|
|
|
// text-strong·12 gate — the donor's L-curve pins step 12 near the ink extreme,
|
|
|
|
|
|
// so AA holds by construction on base-like seeds. The solver's genuine job is
|
|
|
|
|
|
// therefore the text·11 band and robustness on out-of-distribution seeds
|
|
|
|
|
|
// (probed in-browser at F2), NOT the hard gate. Value of this bank: it stays
|
|
|
|
|
|
// green as the base + generator evolve (a regression guard, not a red-to-green
|
|
|
|
|
|
// milestone).
|
|
|
|
|
|
function templatesExcept(
|
|
|
|
|
|
scales: Record<string, StepMap>,
|
|
|
|
|
|
exclude: string
|
|
|
|
|
|
): Record<string, ScaleSteps> {
|
|
|
|
|
|
const out: Record<string, ScaleSteps> = {};
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
for (const name of PALETTE_SCALES) {
|
|
|
|
|
|
if (name === exclude) continue;
|
|
|
|
|
|
const s = scales[name];
|
|
|
|
|
|
if (!s) continue;
|
|
|
|
|
|
const hexes = Array.from({ length: 12 }, (_, i) => s[i + 1]);
|
|
|
|
|
|
if (hexes.some((h) => !h)) continue;
|
|
|
|
|
|
out[name] = scaleToTemplate(hexes as string[]);
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
}
|
|
|
|
|
|
return out;
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const generated: PairResult[] = [];
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
for (const [mode, scales] of MODES) {
|
|
|
|
|
|
for (const scale of PALETTE_SCALES) {
|
|
|
|
|
|
const solidHex = scales[scale]?.[9];
|
|
|
|
|
|
if (!solidHex) continue;
|
|
|
|
|
|
const seed = safeParseColor(solidHex);
|
|
|
|
|
|
if (!seed) continue;
|
|
|
|
|
|
const templates = templatesExcept(scales, scale);
|
|
|
|
|
|
const tplName = pickNearestTemplate(seed, templates);
|
|
|
|
|
|
const gen = generateScale(seed, templates[tplName]);
|
|
|
|
|
|
const genHex: StepMap = {};
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
gen.forEach((step, i) => {
|
|
|
|
|
|
genHex[i + 1] = oklchToHex(step);
|
|
|
|
|
|
});
|
|
|
|
|
|
generated.push(...auditPairs({ [scale]: genHex }, mode, [scale]));
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
const genFails = summarize(
|
|
|
|
|
|
generated,
|
|
|
|
|
|
'\n── Bank 2 · morph-generated, leave-one-out (Stage-2 regression bank)'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
color: contraste Stage 2 — contrato §40 como datos + guard CI; solver descartado
La ejecución refutó la premisa del plan: el morph HEREDA el contraste del texto
(steps 11/12 = curva-L del donante verbatim; ~invariante al croma bajo
gamut-mapping), así que toda escala desde donantes §40-compliant cumple por
construcción. Medido en 3 bancos (base, leave-one-out, 45 semillas OOD): 0 fallos
del gate duro text-strong·12, min WCAG 9.7:1. El solver de luminancia no tenía
nada que resolver → descartado por especulativo. D2 = base verbatim (sin
migración base→seeds).
- $color: CONTRAST_PAIRS (arts/color/contrast-contract.ts) — tabla §40 como datos
compartidos: floor duro text-strong·12, banda text·11 (tope relacional, sin nº
mágico), border·7 exento
- scripts/contrast-audit.ts: muere la PAIRS pre-veredicto (4.5 duro contra §40),
consume CONTRAST_PAIRS + banco de regresión morph-generado
- eidos/lib/contrast-invariant.test.ts: guard de CI (4 tests) que bloquea la herencia
- docs: reference.md §40, next-features.md §1, changelog.md §45, plan (OUTCOME)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 months ago
|
|
|
|
console.log(
|
|
|
|
|
|
`\nSummary: base HARD fails ${baseFails} · generated HARD fails ${genFails} · load-bearing border fails ${lbFails.length}\n`
|
|
|
|
|
|
);
|