контраст: 32 → 2 → 0, проверено на живой странице; сторож теперь ловит выбор токена, а не только его значение #3281

Merged
bot-backend merged 1 commit from fix/mera-contrast-two-leftovers into main 2026-08-30 10:54:59 +00:00
3 changed files with 51 additions and 2 deletions

View file

@ -65,6 +65,11 @@ describe("контраст токенов b2c", () => {
["ink", b2c.ink],
["muted", b2c.muted],
["accentText", b2c.accentText],
// accentDeep — не только заливка кнопки. `.stepsLabelAccent` красит им
// ТЕКСТ, и на surfaceSoft он давал 4.48: сторож проверял его лишь как
// подложку под белым и этого не видел. Токен, используемый двумя
// способами, обязан проверяться обоими.
["accentDeep (как текст)", b2c.accentDeep],
["success", b2c.success],
["danger", b2c.danger],
];
@ -106,6 +111,48 @@ describe("контраст токенов b2c", () => {
expect(contrast(b2c.accentText, b2c.surfaceDark)).toBeLessThan(AA_NORMAL);
});
/**
* Правила ПОД ТЁМНЫМИ СЕКЦИЯМИ не должны краситься светлофоновыми токенами.
*
* Так и появился `.tickerNote` (3.06): подпись добавили в одной ветке, токены
* чинили в другой, каждая по отдельности была права а вместе дали дефект.
* Тест выше его не поймал бы: он проверяет ЗНАЧЕНИЯ токенов, а промах здесь
* в ВЫБОРЕ токена для конкретной поверхности.
*
* Подложку правила из текста CSS не вывести, поэтому список тёмных секций
* задан явно но это снимок СЕКЦИЙ, а не снимок нарушений: секции заводят
* раз в несколько месяцев, а правила внутри них правят постоянно. Новое
* правило в уже известной тёмной секции ловится без обновления списка.
*/
it("в тёмных секциях не используются светлофоновые токены текста", () => {
const css = readFileSync(
join(__dirname, "..", "landing-v3.module.css"),
"utf8",
);
/** Префиксы классов, чьи правила лежат на тёмной подложке. */
const DARK_SECTIONS = ["ticker", "gameRowDark", "pathCardDark", "artCta", "gameMapBottom"];
/** Токены, чья шкала рассчитана на СВЕТЛЫЙ фон. */
const LIGHT_ONLY = ["--b2c-muted", "--b2c-accent-text", "--b2c-ink"];
const нарушения: string[] = [];
for (const [, selector, body] of css.matchAll(/\.([A-Za-z0-9_-]+)\s*\{([^}]*)\}/g)) {
if (!DARK_SECTIONS.some((p) => selector.startsWith(p))) continue;
const color = body.match(/(?:^|;|\n)\s*color:\s*var\((--b2c-[a-z-]+)\)/);
if (!color) continue;
// `--b2c-muted-dark` начинается с `--b2c-muted` — сравниваем точно.
if (LIGHT_ONLY.includes(color[1])) {
нарушения.push(`.${selector}${color[1]}`);
}
}
expect(
нарушения,
"Тёмная секция красится токеном для светлого фона. На surfaceDark шкала " +
"перевёрнута: берите --b2c-muted-dark или --b2c-accent.",
).toEqual([]);
});
it("accent не используется как текст на светлом: он и не может", () => {
// Ради этого в палитре и живёт отдельный accentText. Ассерт фиксирует
// причину, чтобы «упростить до одного акцента» нельзя было молча.

View file

@ -93,7 +93,7 @@ export const b2c = {
/** Акцент — заливки/обводки/декор. НЕ для текста, см. шапку. */
accent: "#16A9BC",
/** Акцент для заливки кнопок (под белым текстом). */
accentDeep: "#0C7B8B",
accentDeep: "#0B7382",
/** Акцент для ТЕКСТА — затемнён до AA. */
accentText: "#0B6B79",
/** Текст на акцентной заливке. */

View file

@ -1414,7 +1414,9 @@
font-family: var(--b2c-font-mono);
font-size: 10px;
letter-spacing: 0.06em;
color: var(--b2c-muted);
/* Плашка тикера тёмная (#121A1D) шкала здесь ПЕРЕВЁРНУТА, и `muted`
(светлофоновый токен) даёт на ней 3.06. Светлый конец палитры: 6.20. */
color: var(--b2c-muted-dark);
}
.tickerBadge {
position: absolute;