CV Hub
Резюме как код. Профессиональная идентичность как воспроизводимая версионируемая система.

Обзор
CV Hub — это не шаблон портфолио. Это система. Проект переосмысляет резюме как инженерный артефакт — версионируемый, воспроизводимый и развёртываемый. Вместо того чтобы поддерживать разрозненные представления в LinkedIn, PDF, Notion и на сайтах-портфолио, CV Hub вводит единый декларативный источник правды — YAML-файл, который управляет всем пайплайном вывода. Результат — полностью автоматизированная система, где одно изменение согласованно расходится по всем форматам и всем ролевым вариациям. Сама страница, которую вы читаете, — часть этой системы: этот кейс собран из блочного YAML-документа тем же движком, что строит резюме.
Проблема
С точки зрения систем традиционные резюме фундаментально сломаны. Они: - дублируются между платформами - синхронизируются вручную - зависят от контекста (DevOps vs Backend vs GameDev) - не находятся под версионным контролем - не воспроизводимы Это ведёт к рассинхрону, несогласованности и заметным накладным расходам на поддержку. С инженерной точки зрения это эквивалентно запуску нескольких несинхронизированных окружений без единого источника правды.
Решение
CV Hub применяет инфраструктурные принципы к персональным данным. Базовая идея проста: опиши один раз → сгенерируй всё → разверни автоматически. Один YAML-файл становится: - живым сайтом - артефактами PDF, DOCX и TXT - несколькими ролевыми версиями резюме - версионируемым профилем под контролем Git Система устраняет дублирование и обеспечивает согласованность by design.
Мультипрофильная система
Архитектура base + delta
Ролевые резюме генерируются по двухслойной модели: базовое CV плюс дельта-переопределения на каждый профиль. Это даёт точный контроль над позиционированием без дублирования данных.

Как работают профили
Каждый профиль объявляется один раз в profiles.yml — id, URL-slug и ключ spec. На этапе сборки merge.mjs проходит по каждой комбинации профиль × язык и создаёт нормализованный артефакт на пару.
Слияние намеренно явное, а не магическое:
- Скалярные и списочные поля дельты целиком заменяют базовое значение.
- Записи опыта сопоставляются по названию компании — дельта-запись, в которой указана только компания, наследует базовую и переопределяет лишь те поля, что задала.
- Список опыта профиля полностью определяет свой упорядоченный набор, так что каждая роль курирует ровно ту историю, которая ей релевантна.
Вывод детерминирован: один и тот же источник всегда даёт одни и те же артефакты, а гард на этапе сборки следит, чтобы routing-slug профиля и его file-spec ключ не расходились — страница профиля никогда не разъедется с его CV и файлами для скачивания.
Интернационализация
Языки — это ось первого класса, а не довесок. Система рендерит матрицу N профилей × N языков из единой базы контента.
- Реестр languages.yml задаёт доступные языки и язык по умолчанию; переключатель языка в шапке генерируется из него.
- Каждая строка интерфейса живёт в translations.yaml и резолвится через небольшой хелпер makeT() с цепочкой фоллбэка: запрошенный язык → английский → сам ключ, так что отсутствующий перевод деградирует мягко, а не ломает страницу.
- Маршрутизация полностью статическая. [...slug].astro выпускает по странице на каждую комбинацию профиль × язык, а главная на языке по умолчанию остаётся на чистом корневом пути.
Китайские переводы уже лежат в слое контента и готовы к включению добавлением одной строки в реестр языков.
Что я построил
- Static-first архитектура с нулём клиентского JavaScript по умолчанию
- YAML как единый источник правды для всех данных резюме
- Мультипрофильная система (DevOps, GameDev и т.д.) через детерминированный пайплайн слияния
- Поддержка нескольких языков с мягкой цепочкой фоллбэка
- Автоматическая генерация документов (PDF через Playwright, DOCX/TXT через docx.js)
- Блочный движок кейсов, на котором работают эти Deep Dive страницы
- Четыре взаимозаменяемых анимированных фоновых компонента (CSS и Canvas)
- Переключение тем через URL с шарингом состояния
- Полностью автоматизированный CI/CD на GitHub Actions с алертами в Telegram
- Динамическая конфигурация по окружению (siteUrl из GITHUB_REPOSITORY)
- Одноколоночный ATS-safe PDF рядом с полированным двухколоночным, из тех же данных
- Онбординг в одну команду (
npm run init) — чистит пример и стартует форк с чистого листа - Автогенерируемые превью-карточки для соцсетей на каждый язык из реальных данных CV — без дизайнера
schema.org/PersonJSON-LD,hreflangи SEO-метаданные на каждую страницу вместо одной общей строки- Готовность к печати —
Ctrl+Pдаёт чистый документ, а не скриншот тёмной темы - Поддержка своего домена (
SITE_URL/BASE_PATH) поверх дефолтного GitHub Pages URL
Архитектура
Система построена как детерминированный пайплайн. Слой данных: - YAML-файлы (базовое CV + дельты профилей, проекты showcase, кейсы, переводы, changelog) Слой обработки: - merge.mjs → нормализованные артефакты на каждый профиль и язык - генераторы документов → создают PDF / DOCX / TXT из этих артефактов Слой представления: - статическая сборка Astro → предрендеренный HTML, контент валидируется типизированными схемами Развёртывание: - GitHub Actions → неизменяемая сборка → GitHub Pages Никакого runtime-сервера. Никакого скрытого состояния. Никакой базы данных. Каждый вывод воспроизводим из исходника, а форма контента проверяется Zod-схемами ещё до сборки страниц.
Динамические фоны
Атмосферу сайта создаёт небольшая библиотека взаимозаменяемых фоновых компонентов. Активен всегда один — они спроектированы как drop-in палитра, каждый самодостаточен и настраивается через ту же систему дизайн-токенов, что и остальная стилизация. Семейства два: CSS-слой, который почти ничего не стоит, и набор Canvas-рендереров, вдохновлённых интерфейсом PlayStation XMB. Каждый показан ниже.
AnimatedBackground — по умолчанию
Чистый CSS · ноль JavaScript
Четыре крупных орба из радиальных градиентов дрейфуют за контентом, поверх — тонкий слой шума. Это полностью CSS — без JavaScript, без canvas, без работы в основном потоке.
Это и самый оптимизированный фон: keyframes анимируют только смещение (без scale), поэтому браузер кеширует каждый размытый орб как текстуру и просто двигает её, а не пересчитывает blur 60px каждый кадр. Весь слой отключается ниже 768px и уважает prefers-reduced-motion.

GalaxyBackground
Canvas · requestAnimationFrame
Звёздное поле на canvas с медленным параллакс-дрейфом, подкрашенное под акцент активной темы. Более тяжёлый, кинематографичный вариант — когда атмосфера важнее стоимости простоя.

PlayStationWaves
Canvas · заливные волны в духе XMB
Заливные синусоиды, наслоённые как меню PlayStation XMB, со сдвигом оттенка по времени суток и недетерминированной стартовой позицией (сид от таймстампа) — так что два запуска не выглядят одинаково. Около 18 пропсов открывают число волн, скорость, амплитуду, цвет и качество отрисовки.

WaveLines
Canvas · светящиеся линии в духе XMB
Штриховой родственник PlayStationWaves: светящиеся линии, симметрично расположенные вокруг центральной оси. Свечение каждой линии композитится на собственном offscreen-canvas — чтобы обойти тонкий баг, где общая маска destination-in съедала альфу фона по краям.

Инженерия производительности фонов
Красивые фоны легко сделать тормозящими, поэтому они рассматриваются как отдельная поверхность производительности.
- CSS-орбы избегают пересчёта blur каждый кадр, анимируя только трансформации.
- Полноэкранный градиент живёт на fixed-псевдоэлементе вместо background-attachment: fixed, поэтому не перерисовывается при скролле.
- Полноэкранный проход mix-blend-mode был убран, как только подтвердилось: при своей прозрачности он невидим, но дорог каждый кадр.
- Каждый анимированный фон отключается на маленьких экранах и уважает reduced-motion.
Итог: насыщенное движение на мощных десктопах и спокойный, дешёвый рендер на всём остальном.
Темы
Темизация — это набор CSS-переменных поверх токен-базы. Тема выбирается параметром ?theme= в URL и применяется крошечным инлайн-скриптом, что делает состояние шарящимся: отправь ссылку — и получатель увидит ровно ту палитру, что выбрал ты.

Пайплайн экспорта документов
Тот же смерженный YAML, что рендерит сайт, генерирует и файлы для скачивания — так веб- и офлайн-версии не могут разойтись.
- PDF получается рендерингом print-вёрстки в headless Chromium через Playwright — пиксель-в-пиксель с сайтом.
- DOCX собирается программно через docx.js — это настоящий редактируемый документ Word, а не скриншот.
- TXT отдаётся как чистый текстовый фоллбэк для ATS-систем и копипаста.
Каждая пара профиль × язык получает свой набор файлов, генерируемых в CI и подключённых прямо на соответствующей странице.
Предпочитаешь Ctrl+P? Живая страница тоже печатается чисто — отдельный print-стиль убирает обвязку (шапку, футер, анимированный фон) и переопределяет все цветовые токены под бумагу, без отдельного шаблона на поддержке.
Свой файл — своей аудитории
PDF и DOCX — то, что узнаёт посетитель: полированное резюме и редактируемая версия, под которую рекрутинговые агентства переверстывают документ. ATS-safe одноколоночный PDF и текстовый экспорт — совсем другая аудитория: пригождаются в основном самому владельцу CV — вставить в форму джоб-портала или в textarea «paste your resume» — а не то, что кликнет рекрутер, листающий сайт: «ATS» для него звучит как жаргон, а не выгода.
Какие форматы показывать и в каком порядке — настройка site.yml, а не зашитый ряд кнопок: личный сайт оставляет только два узнаваемых посетителю формата, демо-деплой может включить все четыре, чтобы показать ATS-вариант. PDF при этом всегда остаётся единственной визуально «сплошной» кнопкой.
Превью-карточки для соцсетей
Ссылка при шаринге должна что-то показывать. Каждая сборка скриншотит реальную, живо отрендеренную страницу через headless-браузер — без дизайнерского инструмента, без статичного шаблона — и компонует её в рамку с обоями 1200×630, используя дизайн-токены активной темы, так что карточка всегда синхронна с тем, как сейчас темизирован сайт.
Одна карточка на язык, из реального CV дефолтного профиля (не из мок-персоны) — любая страница, включая профильные и кейсы, подбирает карточку своего языка автоматически через тот же фоллбэк og:image.
Находимость
То, что не видно, пока не заглянешь в dev tools: своё meta-описание и title на каждой странице вместо одной общей строки на весь сайт, schema.org/Person JSON-LD на CV-страницах — чтобы поиску по имени было что показать, hreflang-альтернативы, прокинутые прямо из тех же данных, что двигают переключатель языка, canonical-URL на каждом маршруте без случайных двойных слэшей, и sitemap, который не расходится с тем, что реально задеплоено.
CI/CD и автоматизация
Релиз — это один git push.
- GitHub Actions гоняет весь пайплайн на каждый push в main: merge → генерация документов → рендер PDF → статическая сборка → деплой на Pages.
- Бинарь Playwright Chromium кешируется по хешу lockfile, экономя около двух минут на холодном деплое.
- Telegram-бот сообщает результат — успех, провал сборки или провал деплоя — с веткой и коротким SHA коммита, молча деградируя, если секреты не настроены.
- siteUrl и base-путь выводятся из GITHUB_REPOSITORY, так что форк собирается и деплоится корректно без всякой конфигурации.
- Sitemap, покрывающий каждый маршрут профиль × язык, генерируется на сборке, с соответствующим robots.txt.
Инженерия производительности
Производительность измеряется, а не предполагается. - Статический предрендеренный HTML без стоимости гидратации по умолчанию. - Первая обложка Showcase с реальной картинкой грузится eager с высоким приоритетом, остальные остаются lazy — намеренная оптимизация LCP. - Композитинг при скролле был профилирован, дорогие слои (перерисовка fixed-градиента, полноэкранный blend-проход) убраны. - Итог: Lighthouse 100 Performance / 100 Accessibility / 100 SEO, с движением, которое остаётся плавным на слабом железе.
Движок Showcase
Портфолио-половина сайта — та же идея, применённая рекурсивно. Проекты объявляются YAML-списком, и каждый может нести полноценный Deep Dive кейс — как этот — собранный из небольшого набора контент-блоков: text, image, video, code, divider.
Блоки валидируются типизированной схемой и рендерятся отдельными компонентами, а значит длинный редакторский кейс пишется целиком в данных, без какого-либо кастомного кода страницы. Ведёт себя как контролируемая статическая альтернатива странице Notion: портативно, версионируемо и быстро.
video встраивает YouTube-ссылку напрямую как адаптивный плеер — без хостинга своего файла, что очень пригодилось при тестировании:

Инженерные решения
- Astro вместо SPA-фреймворков — чтобы убрать лишний рантайм и стоимость гидратации
- Статическая генерация — ради гарантии производительности и предсказуемости
- YAML вместо CMS — ради полного контроля и Git-нативного версионирования
- Состояние через URL вместо localStorage — ради шаринга и прозрачности
- Явные дельта-файлы вместо неявных переопределений — чтобы изменения были наблюдаемы
- Типизированные схемы контента — чтобы кривые данные роняли сборку, а не браузер
DevOps-подход
Проект относится к резюме как к build-артефакту. Ключевые принципы: - версионирование на Git - неизменяемые сборки - автоматизация CI/CD - развёртывание, независимое от окружения - нулевые runtime-зависимости Каждый коммит порождает полностью согласованное, развёртываемое состояние системы.
Компромиссы
- Нет CMS — намеренный компромисс ради контроля и прозрачности
- Нет бэкенда — чисто статическая архитектура
- Ручное создание дельт для профилей — явность вместо неявной сложности
- Canvas-фоны дороже CSS-дефолта — поэтому один по умолчанию, остальные по выбору
- Ограниченная гибкость в рантайме в пользу детерминизма
Результат
- Одна правка перегенерирует все форматы по всем профилям и языкам
- Полностью статический деплой с нулевой стоимостью инфраструктуры
- Готовая к форку система, не требующая конфигурации
- Lighthouse: 100 Performance / 100 Accessibility / 100 SEO
- Предсказуемый, воспроизводимый вывод между окружениями
Выводы
CV Hub показывает, что даже нетрадиционные домены вроде резюме выигрывают от инженерной строгости. Применяя принципы системного дизайна — единый источник правды, детерминированные пайплайны и автоматизацию — проект превращает статический документ в масштабируемую, поддерживаемую систему. Этот подход обобщается за пределы резюме на любой домен, где важны согласованность данных, воспроизводимость и вывод в несколько форматов.
Хочешь такое же?
Всё на этой странице — этот кейс, резюме за ним, даже рикролл парой секций выше — живой инстанс CV Hub. Если у тебя есть пять минут и ты умеешь пользоваться git, можешь получить свой.
1. Форкни
Сделай fork репозитория на GitHub, затем клонируй свой форк.
git clone https://github.com/YOUR_ACCOUNT/cv_hub.git
cd cv_hub
2. Начни с чистого листа
npm install, затем npm run init — чистит пример CV, шоукейса и профилей, что идут вместе с репозиторием, и подставляет вместо них пустой плейсхолдер, так что правишь своё, а не чужое резюме. Сначала всё бэкапит; npm run init -- --dry-run показывает, что будет сделано, ничего не трогая.
npm install
npm run init
3. Заполни данные
Всё живёт в одном файле: src/content/cv/ru.yaml. Три способа заполнить его — выбери, какой удобнее:
Редактировать YAML напрямую— имя, summary, опыт, навыки. Обычный текст, не нужен билд, чтобы увидеть форму.Импортировать JSON Resume— уже есть такой?npm run resume:importсконвертирует его.Отдать нейросети— вставь своё текущее резюме (PDF или обычный текст) в Claude или ChatGPT вместе с промптом изdocs/LLM-CONTEXT.md. На выходе — готовый к использованию YAML. Реально самый быстрый путь, если не хочешь трогать схему руками.
4. Посмотри
Открой http://localhost:4321 — каждое сохранение видно сразу.
npm run dev
5. Задеплой
Включи GitHub Pages один раз — Settings → Pages → Source: GitHub Actions — а дальше просто пушь. Каждый коммит в main пересобирает и передеплоивает сайт автоматически. Больше никогда не нужен отдельный шаг деплоя.
git add .
git commit -m "my resume"
git push