контраст: два оставшихся нарушения на живой странице + сторож по секциям
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m0s

Проверка axe-правилом по ОТРЕНДЕРЕННЫМ узлам прода дала 32 → 2. Обе
оставшиеся показательны, и одну из них завёл мой же вчерашний батч.

.tickerNote (3.06) — подпись под тикером. Её добавила одна ветка, токены
чинила другая; каждая по отдельности была права, вместе дали дефект: подпись
взяла --b2c-muted (токен для СВЕТЛОГО фона) и легла на тёмную плашку #121A1D,
где шкала перевёрнута. Красится --b2c-muted-dark, 6.20.

.stepsLabelAccent (4.48) — «ШАГ 3 · 150 ₽» на карточке #EAF5F7. Сторож знал
эту поверхность, но проверял accentDeep только как ЗАЛИВКУ (белый по нему), а
CSS использует его ещё и как ЦВЕТ ТЕКСТА. accentDeep #0C7B8B → #0B7382:
минимальный шаг, проходит текстом на всех трёх светлых поверхностях
(4.99/5.15/5.54) и заодно улучшает главную кнопку — белый по нему 4.97 → 5.54.

Две правки сторожа, обе про механизм, а не про случай:
1. accentDeep проверяется и как текст: токен, используемый двумя способами,
   обязан проверяться обоими.
2. Новый тест «в тёмных секциях не используются светлофоновые токены». Он
   ловит промах в ВЫБОРЕ токена, а не в его значении — тот класс, который и
   прошёл мимо. Список тёмных секций явный, но это снимок СЕКЦИЙ, а не
   нарушений: новое правило внутри известной секции ловится без правки списка.

Фальсификация обеих: возврат accentDeep даёт «4.4775 >= 4.5», возврат
.tickerNote на muted даёт «.tickerNote → --b2c-muted». Первую попытку
фальсификации засчитывать было нельзя — подмена не сработала из-за
добавленного комментария, и тест остался зелёным по построению.
This commit is contained in:
bot-backend 2026-08-30 15:51:25 +05:00
parent de31f2791b
commit 3b09368669
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;