Этот сайт
Портфолио, собранное как продукт: статическая сборка, свой вид у каждой страницы, открытый журнал решений и проверки перед каждым коммитом.
- Что это
- Сайт на двух языках. Главная, Флоу и Проекты готовы, «Пора на пару» пока заготовка. Это файлы без сервера приложений, их отдаёт Caddy.
- Моя роль
- Дизайн, вёрстка, движение, инфраструктура и тексты.
- Что открыто
- Код, журнал решений и план лежат на GitHub. У каждого раздела есть ссылки на его файлы.
- Период
- с 30.10.2026
- Адрес
- finchaf.com
- Стек
- Next.js, TypeScript, Tailwind, GSAP, Caddy
- 135коммитов
- 14решений
- 14проверок
- 557тестов
- 4темы
- 2языка
На 03.11.2026, ветка main и прогон check.py
Форма
У каждой страницы своя форма
Портфолио обычно рассказывает о работе словами. Здесь страница устроена как сама работа: главная это сцена с прокруткой вниз и вбок, Флоу выглядит как Obsidian, потому что тот кейс и был базой заметок, а Проекты похожи на редактор, потому что я дизайнер.
Цена такой формы: у каждой страницы свой движок. Чтобы они не срослись, страницы разделены границами. Фича не импортирует другую фичу, а общее переезжает ниже, в ui/ и lib/. Это не договорённость, а правило ESLint: импорт через границу роняет проверку.




Файлы и решения
Темы
Один компонент, четыре темы
Каждая страница объявляет свою тему, и тема меняет цвета, гарнитуры и вид контролов. Режимов два, поэтому сочетаний восемь. Компонент о темах не знает: он читает токены, а значения лежат в файле темы. Новая тема это один файл.
Цвет вне контракта просто не соберётся: палитра Tailwind отключена. Контраст считает тест, и ради него у темы два акцента. accent это точный цвет бренда для заливок, accent-strong это вариант для текста. Белый на синем продукта даёт 3,7:1, для текста этого мало.
Тема объявлена в разметке и читается через :has(), поэтому она есть в первом HTML и страница не мигает. Кнопки \Написать на главной и [[Написать]] на Флоу это один компонент, а капсула меняет обшивку на лету, когда на главной приезжает глава другой темы. Значения ниже считаны с примера при каждом переключении.
Заголовок страницы
Тот же абзац, та же разметка, те же компоненты.
Линза
Дизайн и код на одном экране
Первый экран это два слоя: дизайн и его чертёж с размерами и настоящими строками кода. Тест находит эти строки в исходниках, поэтому чертёж не расходится с кодом.
Слои совпадают по координатам: каждый блок чертежа стоит на месте настоящего, потому что внутри него лежит сам этот блок, скрытый, и задаёт размер. Линза только открывает второй слой через clip-path. Она достаёт и до капсулы, хотя та лежит вне первого экрана: скрипт пишет ей те же переменные в её координатах.
Первый экран не ждёт анимацию. Имя есть в HTML и отрисовано с первого кадра, а сборка лишь стирает с него контур. Настоящие контролы лежат под слоем кода и принимают клики, а фокус с клавиатуры поднимает их поверх.




Проведите линзой по кадру
Движение
Смена страницы по косой логотипа
У каждого инструмента своя зона: наведение и фокус делает CSS, смену страницы ViewTransition, сцены на прокрутке GSAP, графы и линии canvas. Длительности и кривые живут в токенах, в коде эффекта чисел нет.
Первая смена страницы выглядела плохо. ViewTransition вокруг страницы даёт один слой на всю высоту, у главной это 34 678 px на 03.11.2026, и браузер сжимает его до экрана. Теперь снимается только экран, а новая страница приходит по линии 20,2°, наклону косой из логотипа. Шаг назад по капсуле идёт в обратную сторону.
В Firefox форма стояла в конце всего перехода: он не интерполирует зарегистрированную переменную @property на этих псевдоэлементах. Поэтому формы выписаны прямо в ключевых кадрах polygon. Снимок WebDriver этого не показывает, такой кадр берётся системным снимком окна настоящего браузера.


Снимается только экран, новая страница приходит по косой.
Проверки
14 проверок, одна команда
python3 scripts/check.py идёт перед каждым коммитом, и CI запускает её же на PR. Ниже настоящий прогон от 03.11.2026: 300 модульных тестов и 257 браузерных.
Браузерные проверки идут за настоящим Caddy с образом того же digest, что на сервере: адреса, заголовки, CSP, доступность в обоих режимах, сцены при уменьшенном движении и без JavaScript. Проверка должна уметь падать: каркас проверялся намеренным импортом через границу и текстом в разметке.
python3 scripts/check.py
Docsok0.1 s
Publicok0.2 s
Typesok2.3 s
Lintok13.7 s
Formatok2.2 s
Contentok0.0 s
Unitok1.0 s
Buildok11.3 s
Bundleok0.3 s
Addressesok15.5 s
SEOok9.1 s
A11yok35.8 s
Motionok210.2 s
Themesok36.8 s
14 из 14, 5 мин 39 с
предел: 280 КБ
Что нашлось по дороге
- 664494f
На карточке Флоу симуляция графа заканчивается на разных координатах на arm64 и x64. На ноутбуке было зелено, в CI красно. Теперь хэш считает, из чего граф состоит, а не где он оказался.
- da37183
Тестовый конфиг отличался от продакшена, и гейт оставался зелёным, хотя на сервере ответ 404 назвал бы сервер и потерял HSTS. Теперь блок заголовков совпадает построчно, а гейт смотрит на заголовки 404.
- 3e2a2bf
Gecko случайно теряет тонкий диагональный градиент, и штриховка рассыпалась в Firefox. Теперь это SVG-маска, а тест не пускает такие градиенты в стили.
- bbdeab3
Тени брали цвет текста, и на тёмном фоне это было свечение. Теперь в тёмном режиме тень прозрачна.
Файлы и решения
Решения
Сначала решение, потом код
Всё, что дорого откатывать, записано до кода: контекст, варианты, выбор и условие пересмотра. Принятая запись не переписывается, другое решение это новая запись.
Статика выбрана не ради экономии: ресурсов на сервере хватало. Она быстрее, надёжнее и безопаснее для соседа по серверу, а анимации и так живут в браузере. Репозиторий открыт с первого коммита, потому что закрытое, попав в историю, остаётся в старых коммитах. Код под MIT, тексты и изображения под защитой, а GSAP остаётся зависимостью и в репозиторий не копируется.
- 0001База знаний в docs/
- 0002Статическая сборка, отдаёт Caddy
- 0003Одно приложение без монорепо
- 0004CSS, Motion, GSAP и WebGL, у каждого своя зона
- 0005Выкатка атомарно, переключением ссылки
- 0006Домен, защитный .ru и почта
- 0007Гейты из одной команды
- 0008Репозиторий открыт
- 0009Страница это фича, границы импорта
- 0010Тема на страницу, два режима, один контракт
- 0011Обшивка контролов это токены темы
- 0012Превью ссылок из токенов сайта
- 0013Флоу как хранилище заметок
- 0014Проекты как редактор
Файлы и решения
Выкатка
Релиз это одно переключение
Сайт это папка файлов на общем сервере рядом с другим продуктом. Выкатка не трогает ни Caddy, ни контейнеры: новая сборка ложится отдельной папкой, а затем одной операцией переключается ссылка current. Простоя нет.
Хранятся три последних релиза, и --rollback возвращает прошлый за секунду. Ключа от сервера в GitHub нет: CI только проверяет, а выкатка идёт с ноутбука. Это осознанная плата: разработчик один, а сайт собирается заново из любого коммита.
- Отказ, если ветка не main, дерево грязное или main расходится с GitHub
- Все проверки и сборка
- Копия в releases/ в папку коммита, одинаковые файлы жёсткими ссылками
- Атомарное переключение ссылки current
- Проверка настоящего домена: коды, CSP, редиректы
- Не прошло: ссылка возвращается на прошлый релиз
ls portfolio/releases
664494f84cee← current
252bf73f2ffc
1a4994f45e0d
Стадия
Что ещё не готово
Сайт в работе, и это видно по плану в репозитории.
- Страница «Пора на пару» пока заготовка.
- Снимки истории выше бюджета: 2,12 МБ всего при пределе 1,5 МБ, замер 01.11.2026.
- Смена страниц и глава Флоу ещё не проверены на живом телефоне и в настоящем Safari.
Файлы и решения