Программа Матемаркетинг 2026 — статическая копия
▶ Живая копия — открывается на любом хостинге, без сборки и установки. Код: NikitaBoyarkin/mm-program-page.
Ситуация
Страница программы конференции — типичный SPA: в HTML приходит пустой <div id="app">, программа
подтягивается из API и рисуется в браузере. Скопировать такую страницу «сохранением вёрстки» нельзя:
в сохранённом файле не будет ни докладов, ни сетки. Обычный ответ на такую задачу — «сделал похоже»,
и проверить это утверждение нечем.
Задача
Разобрать интерфейс до измеримых величин — размеров колонок, отступов, высоты строки, кеглей — и собрать копию, которая работает без сборки, без бэкенда и без npm. И, главное, получить проверяемое утверждение вместо «похоже»: замеры, тесты и зафиксированный список отклонений.
Действия
- Разведка. Определил, что страница — Vue-приложение, а программа приходит из
GET /api/event-program/event/6/: 335 КБ JSON, 3 дня, 91 доклад, 11 треков, 9 залов. Дизайн-токены (границы#E5E7EB, фон сетки#F9FAFB, бренд#5C34F6) вытащил из конфигурации визуала и боевого CSS, а не подбирал на глаз. - Данные.
tools/fetch_program.pyкачает payload, чистит HTML из описаний, склоняет пустойshort_descriptionк первому абзацу и собираетwindow.MM_PROGRAM. Режимы--fetch,--check(ловит дрейф между слепком и API) и--dry-run. - Вёрстка. Скелет
index.html+ курированный CSS (выгрузка боевого CSS со снятымиdata-v-*и выброшенными Vuetify-внутренностями) +app.jsбез зависимостей: сетка «время × залы», multi-slot карточки, табы дней, 11 чипов треков, избранное вlocalStorage, модалка с фокус-трапом, экспорт дня в.ics. - Проверка. Четыре независимых прохода: ревью кода (15 находок, 2 высоких), security-аудит
(0 блокирующих), браузерные тесты на headless Chrome и визуальный аудит с замерами
в пикселях. По находкам добавил валидацию URL картинок, фолбэки битых баннеров и аватаров,
фолдинг строк в
.ics,ariaдля табов и CSP.
Результат
Сетка воспроизведена по замерам, а не по ощущению:
| Метрика (1440 px) | Оригинал | Копия |
|---|---|---|
| Колонка времени | 99 px | 99–100 px |
| Колонка зала | 420 px | 420 px |
| Карточка и радиус | 391 px, 16 | 391 px, 16 |
| Баннер | 4:3, 291 px | 4:3, 291 px |
| Строка сетки | 550 px | 550 px |
| Верх сетки | y = 414 | y = 414 |
| Заголовок (ink-бокс) | 638×22 | 650×22 |
| Высота документа | 6605 px | 6589 px |
Проверки: tests/test_data.py — 9 тестов, 2370 проверок инвариантов данных;
tests/test_page.py — 7 тестов на реальный рендер (счётчики по дням, целостность grid-area,
скриншоты на 1440 и 390 px, деградация без данных). Оба набора проверены мутациями:
сломанный счётчик, сдвинутая сетка и пустой заголовок валят соответствующий тест.
Ограничения
- Нет бэкенда: авторизации, личного кабинета, серверного избранного, чата и плеера в копии не будет.
- Картинки и коммерческий заголовочный шрифт не перепубликуются — грузятся с CDN первоисточника. Из-за CORS оригинальный Loos Wide не загружается, поэтому заголовок рисует свободный Unbounded, подогнанный по ink-боксу (650×22 против 638×22).
- Счётчик в шапке показывает число докладов дня, а не «людей онлайн»: присутствие в статике выдумывать нельзя.
- Данные — слепок на дату сборки.
python3 tools/fetch_program.py --checkпокажет, если программа на первоисточнике изменилась.
Документация
- Код и тесты —
index.html,assets/app.js,tests/,tools/fetch_program.py - CONTRACT.md — схема данных, DOM-контракт и список поведения
- docs/DECISIONS.md — все отклонения от оригинала с причинами
- Живая копия — открывается без установки