По любви · Android × Figma · 10.09.2026
Ещё десять экранов ленты — от шторки записки до пейволла — собраны из данных нод: переменных, стилей, компонентов, матриц обрезки, дуг и эффектов. Каждый кадр снят на устройстве в честном масштабе ×3 (1170×2532) и сведён с рендером Figma. Ниже — кадр рядом с рендером, четыре числа на кадр и разбор того, что оказалось не так. Главная находка партии: затемнение полноэкранного кружка видео размывает подложку — из-за этого шесть кадров полгода числились «непереводимым остатком рендерера». Это продолжение артефактов по кадрам S01–S10, S11–S20 и S21–S30.
Смотровая
Тяните шторку, чтобы совместить макет и приложение. «Карта» показывает, где остаётся расхождение по геометрии: синим закрыты системный статус-бар, полоска home, клавиатура и диалоги разрешений iOS — их рисует Android, они в сравнении не участвуют (спека 6.4).
Числа
Полное — доля пикселей, отличающихся сильнее ΔE 6 вне масок. Без текста — то же с исключёнными текстовыми блоками. ±1 px — пиксель считается расхождением, только если не совпал ни с одним пикселем рендера в окне ±1: сдвиг растра на долю пикселя не есть ошибка вёрстки. Геометрия — два последних вместе, то есть чистая форма, положение и материал; по ней и выносится вердикт. Шкала показывает геометрию относительно порога 0,3 %.
| Экран | Кадр | Состояние | Полное % | Без текста % | ±1 px % | Геометрия % | Шкала до порога 0,3 % |
|---|
Разбор
Каждая строка — расхождение из реестра docs/figma/fig/ledger.md:
что показала нода, что делало приложение и сколько это стоило в числах.
Полгода шесть кадров полноэкранного кружка видео числились «классом остатка»: считалось,
что затемнение поверх карточки не выражается никакой заливкой. У ноды overlay
и правда две заливки — #252525 при 0,55 и
#9C9C9C режимом OVERLAY, — но кроме них стоит
BACKGROUND_BLUR радиусом 100. Цвет пикселя зависит от соседей, поэтому
любая поточечная подгонка упиралась в |Δ| ≈ 8,5. Теперь подложка
размывается сама: σ подобрана по рендеру (40 при радиусе 100 — |Δ|
1,08 против 13,31 без размытия), а Modifier.blur на эмуляторе молчит,
поэтому фотография уменьшается в восемь раз, размывается и растягивается обратно.
Крест «дизлайка» рисовался с обрубленными углами. Обводка в Figma стоит по центру
контура и выходит за рамку ноды на половину толщины (7 при обводке 14), а
android:viewportWidth режет по себе. Починка не в вёрстке, а в приборе:
tools/fig_pics.py считает габарит нарисованного — с половиной обводки и с
учётом clip-path, — расширяет вьюпорт, сдвигает контуры группой и пишет поля
в pics/index.json, откуда их берёт раскладка.
Сердце «лайка» в мастере собрано двумя заливками — Pale Beige и Terracotta при 46 %. На кадре «2.5» у той же картинки осталась одна заливка: форма та же, тон другой. Раскладка теперь красит картинку по краске самой ноды и кладёт по слою на каждую заливку, а не полагается на цвет, впечённый в ресурс.
У барабанов фильтров в ноде 116×228, а рендер рисует 151×140: скрытые строки авто-лейаут
не считает, и числа в API остались от старой версии компонента. Замер рендера показал
заливку с 12 до 163 и с 227 до 378 — пара делит 366 между двумя барабанами и чертой 32.
Правило спеки: эталон формы — рендер, а не absoluteBoundingBox.
Фон карточки премиум-фильтров — три терракотовых круга с размытием слоя 120. Плоское приближение Φ((R−d)/σ) здесь не годится: радиус круга того же порядка, что σ, и двумерное размытие срезает вершину — в центре круга 60,5 альфа выходит 0,52, а не 1. Точное значение — Q-функция Маркума; она считается по 24 точкам, между ними идёт радиальная заливка, а σ подобрана по сетке из 110 замеров рендера.
Подвал обучающего видео V3 выходил темнее рендера на четверть. У градиента альфа задана
дважды: у каждого стопа (0,9 → 0,5 → 0,25 → 0) и opacity 0,8 у всей заливки.
Множители перемножаются. Замер по строкам показал ровно эту разницу: 47,2 против 62,5 на
y=700.
У summary двух кадров свайпа стоит counterAxisAlignItems
CENTER, и признак «имя по центру» читался по нему. Но все ряды внутри шириной во всю
карточку (326), и рендер их не двигает — центрировать нужно только там, где по центру стоит
сам ряд name-box. Из-за подмены город уезжал на 127 dp, а имя на 60.
Modifier.fillMaxWidth().width(374.dp) оставляет ширину кадра:
fillMaxWidth задаёт точные ограничения, и width их уже не сужает —
значение ячейки вставало по 390 вместо 374. А PlNodeText центрирует содержимое
в блоке, если не задано выравнивание: «Т-БАНК» оказывался на 152 вместо 60. Вторую ошибку
метрика геометрии не ловит вовсе — текстовые блоки из неё исключены, — её видно только в
полном числе и в отдельной проверке габаритов текста.
Одно и то же место кадра меняет и заливку, и обводку: при загрузке без кадра видео
кружок залит чёрным, у кружка 96 обводка белая 3, у полноэкранного 326 — нулевая, у
ошибки — Beigeish Gray со значком 40 / video-play, у остановленного видео
сверху лежит #000 при 0,35. Лоадер, кнопка звука и «play» — не
части кружка, а отдельные ноды кадра со своими координатами.
У запертых премиальных ячеек меняется всё сразу: замок вместо значка, плашки залиты Brand/Powdery подписью Dark Beige и без обводки, барабаны — Powdery 50 %, ползунок и правая засечка Dark Powdery, закрашенной части дорожки нет вовсе. Это не «состояние disabled» компонента, а другой набор красок в каждой ноде.
«−20 %» над карточкой «3 месяца» терракотовая, когда выбрано это предложение, и чёрная, когда выбрано соседнее. Плашка стоит на 12 выше верхнего края коробки и принадлежит ей, а не строке.
textDecorationСтарая цена «899 ₽» перечёркнута отдельной нодой Line 9 37×11,5
обводкой терракотой 1,33 — линия идёт наискось, снизу вверх. В стиле текста никакого
зачёркивания нет, и подчёркивание средствами шрифта дало бы другую линию.
Чипы «что в подписке» на кадрах ленты залиты Powdery 50 % с подписью Dark Beige, а на кадре из секции «Доработки» — Brand/Terracotta 60 % с белыми значком и подписью. Список тот же, набор тот же — краски разные.
Снек ошибки сохранения фильтров называется вариантом positive, а залит Additional/Error Red. Цвет решается по заливке, а не по имени варианта: иначе красная полоса рисовалась бы чёрной.
Собрано
Кроме состояний уже готовых экранов, в этой партии впервые собраны два больших экрана целиком — из нод, а не с картинки.
Стопка ячеек с чертами, ползунок радиуса с овальным манипулятором 40×38, пара барабанов 151×140 с тремя строками, три состояния переключателя, премиальные ячейки с замками, карточка премиум-фильтров с размытыми кругами, сообщества V4 со значками 32 и длинные кадры V3 в 2948 и 2580 dp — они сняты по частям с прокруткой и сведены с теми же частями рендера.
Заголовок с подписью, два предложения 179×140 с обводкой по центру рамки, плашка скидки, чипы с переносом, условия с двумя кнопками и нижняя панель 390×170, прижатая к низу кадра поверх условий. Ещё два кадра экрана — шторка Apple Pay и страница оплаты в Safari — это чистый системный хром iOS: своей вёрстки они не имеют и в сравнение не идут.
Мера
Число, по которому мы закрываем кадр, считает форму и материал, но исключает текстовые блоки — иначе в него попадала бы разница отрисовки букв. Значит, ошибку в самом тексте оно не поймает. Владелец заметил на одном кадре, что заголовок «Что в подписке» стоит у левого края вместо центра, — и мы прогнали отдельную меру по всем 73 кадрам: она измеряет каждый текстовый блок на снимке против рендера и печатает смещение в долях dp. Из 29 подозрительных блоков после правок осталось 10, и 8 из них измеритель сам помечает как ненадёжные. Проверка добавлена в обязательный ритуал перед закрытием экрана.
Все четыре шторки — жалоба, «Не пропусти симпатию», обновление и техработы — в макете подписаны Manrope 20, а рисовались TT Runs 22. Буквы крупнее, и «На что хочешь пожаловаться?» переносилось в две строки там, где в макете одна.
Блок заголовка 358 с выравниванием по центру: чернила в макете начинаются со 119,6, а у нас стояли на 16. Ошибка была на всех девяти кадрах подписки.
Между карточкой и описанием в колонке стоит ряд из трёх кнопок 80. Сами кнопки экран рисует по координатам, а колонке нужна их высота — без неё всё описание уезжало на 112 dp вверх, и внизу не было ссылки «Пожаловаться».
Подпись шторки записки была серой вместо чёрной; сдвиг строки переключателя на 4 dp оказался свойством самой строки, а не состояния «выключено»; длинный город «Петропавловск-Камчатский» рендер обрезает многоточием, а мы выпускали его под значки; у слова-плашки поверх видео потерялся трек в 1 dp; подпись подсказки рисовалась во всю ширину кадра вместо блока 280 и переносилась не там же; заголовок «В три раза больше записок» переносился на слово позже — у нашего статического TT Runs глифы уже, чем у вариативного в Figma.
Вопросы
Здесь не ошибки вёрстки, а места, где макет спорит сам с собой или с собственным рендером. Мы делаем как в рендере и передаём пару дизайнеру.
| Что | Где | Как сейчас |
|---|---|---|
| Сердце подсказки обучения | 5525:32092 |
в ноде видимое и лежит поверх затемнения, а рендер не рисует его ни на одном из четырёх кадров — в прямоугольнике 64×64 ноль светлых пикселей |
| Кадр «4 video exists» | 6618:19686 |
вся сводка в ноде скрыта и держит прежние данные («Москва», «30», «56 %»), а рендер рисует другие («2 км», «28», «87 %», чип «Книги»); низ фотографии тоже не совпадает с растром из файла — дамп и рендер из разных версий |
| Снек ошибки фильтров | 6245:33379 |
вариант называется «positive», заливка — Additional/Error Red |
| Чипы «что в подписке» | 10346:36366 |
на кадре «Доработки» терракота 60 % с белым текстом, на тех же кадрах ленты — Powdery 50 % с Dark Beige |
| Подпись «from rewind» | 6399:19310 |
«вовзращай» — опечатка в самом макете |
| Строка переключателя в запертой ячейке | 6823:16373 |
сдвинута на 4 относительно такой же строки в открытой ячейке |
Границы
Форма зелёная — это половина работы. Вот честный остаток.
| Что | Почему | Что нужно |
|---|---|---|
Кадр 6618:19686 — 5,265 % |
дамп страницы и рендер кадра из разных версий файла: скрытая сводка держит прежние
данные, а низ фотографии не совпадает с растром из .fig — подбор сдвига не
помогает |
перевыгрузить страницу из Figma либо принять кадр как есть — решение за владельцем |
| Два платёжных кадра | шторка Apple Pay и страница оплаты в Safari — системный хром iOS | на Android им отвечает другой платёжный лист; сравнивать нечего |
| Экраны в приложении | фильтры и подписка собраны и снимаются фикстурой, но в навигацию не включены | заменить прежние FiltersScreen и экран подписки, переснять кадры |
| Поведение экранов | трасса запросов против карт Flutter не снята | файловый лог запросов в debug-сборке и инструмент разбора |
| Плотность чернил на части кадров | форма закрыта, а текст по своей мере выходит за предел | разобрать метрики шрифта на кадрах с дробным кеглем |