---
name: wcag-contrast-over-video
description: Контраст текста поверх видео нельзя мерить статикой — считать по кадрам; и не мерить своей копией градиента вместо стиля со страницы
metadata: 
  node_type: memory
  type: feedback
  originSessionId: b11e39bd-4f6f-47c9-ba7b-680ccf041a5b
  modified: 2026-08-06T11:34:46.840Z
---

Проверяя контраст текста, лежащего на **видео** (герой codoceh.ru: белый H1 на `forge.mp4`), обычный обход DOM даёт мусор: скрипт поднимается по родителям в поисках непрозрачного фона, не находит и берёт фон `body`. Правильный способ — отрисовать кадр видео в canvas, наложить тот же затемняющий градиент, найти **самый светлый пиксель** под текстовым блоком и считать контраст с ним, повторив это на 5–8 позициях `video.currentTime`.

**Why:** 2026-08-06 замер по DOM показал «1.08:1» (ложь), а покадровый — 2.99:1 на светлых кадрах при норме 3:1 для крупного текста. Дефект реальный: на части кадров заголовок терял читаемость. Лечится не цветом текста, а **мид-стопом затемняющей подложки** (0.42→0.60) — исправлено, худший кадр стал 5.27:1.

**Вторая ловушка, дороже первой:** в первой версии скрипта градиент был **захардкожен копией** значений из CSS. После правки CSS замер выдал те же самые числа — и это читалось как «фикс не сработал». Правило: измерительный скрипт обязан читать параметры из **живой страницы** (`el.style.background` / `getComputedStyle`), никогда из своей копии. Родственно [[kodoceh_web_dead_tokens_json]] и [[deployed_is_not_running]]: совпадение значения в двух местах — это не проверка, а два независимых источника вранья.

**Третья ловушка, того же рода — липкая шапка.** Скрипт резолвил фон по цепочке предков и для `position: sticky` шапки доходил до `<main>` со светлым фоном, хотя визуально под ней тёмная плита. Итог — 6–7 ложных «контраст 1.1» на пунктах меню **на всех четырёх сайтах сразу** (одинаковость находки и была подсказкой, что врёт инструмент). Починка в `~/Services/shared/tools/impeccable/verify_any.py`: для элемента внутри `sticky/fixed` сначала ищем непрозрачный фон **внутри самого оверлея** (у кнопки он свой), и только если его нет — берём то, что реально нарисовано под текстом через `elementsFromPoint`, пропуская узлы самой шапки. Первая версия правки пропускала оверлей целиком и сломала обратное: кнопка со своим фоном стала мериться о плиту под ней.

**How to apply:** любой замер «до/после» сначала прогонять на заведомо изменённом входе и убеждаться, что число сдвинулось. Не сдвинулось — подозревать инструмент, а не правку. И наоборот: одна и та же находка на всех объектах сразу — почти всегда дефект измерителя, а не всех объектов.
