Как сделан этот сайт: Astro, один файл токенов и агент, который идёт по брифу
Статический сайт на Astro с нативным CSS, сферой на three.js и курсорным полем. Что в стеке, как я работал с Claude Code и как довёл LCP с 2,6 до 1,66 секунды.

Этот сайт это статический проект на Astro с нативным CSS и несколькими маленькими библиотеками. Без React, без Tailwind, без GSAP. Единственная тяжёлая зависимость это three.js, и нужна она только для сферы в боковой панели.
Я перенёс его с Framer на собственный код и заодно обновил. Арт-дирекшн и бриф мои. Основную часть кода писал Claude Code, сессию за сессией.
Портфолио у меня раньше жило на Framer. Меня оно устраивало, но гибкости не хватало, и многое из задуманного там сделать было нельзя. Когда я освоил Claude Code, я пересобрал сайт на собственном стеке. Подписки нет, я контролирую каждый слой, а заодно хотел нормально разобраться с three.js. Ещё я хотел живой, отзывчивый сайт, который вовлекает, отдельный проект, а не галерею картинок и историй.
Сначала бриф, потом сессии
До первой строки кода я написал бриф: стек, арт-дирекшн, значения анимации, страница за страницей, цели по производительности. Потом разбил работу на 12 сессий. У каждой есть чёткая цель, и она добавляет строку в журнал прогресса. В журнале лежат и открытые вопросы ко мне.
Правила, которые агент не должен нарушать, лежат в файле CLAUDE.md в репозитории:
- оранжевый только в сфере, курсорном поле, прелоадере и выделении текста;
- каждая длительность, кривая и пружина берётся из токенов, никаких магических
0.35s; - у каждого интерактивного элемента есть hover, press и focus, реакция за 100 мс или быстрее;
- цифры кейса живут только в его
meta.yml; - без выдуманного контента, только реальные факты, которые не публикуются без живой модерации.
Один источник токенов
Цвета, шрифты, отступы, длительности и пружины лежат в JSON-файлах, которые выгружаются из моей дизайн-системы. Скрипт превращает их в CSS custom properties перед каждым запуском dev и сборкой, поэтому значение печатается один раз. Значения анимации лежат в одном файле, который читают и TypeScript-модуль анимации, и скрипт токенов.
:root {
--dur-fast: 200ms;
--ease-hb-out: cubic-bezier(0.12, 0.23, 0.5, 1);
}
Типографика на clamp(), пружины экспортируются как CSS linear(). CSS разложен по слоям от reset до motion, а слой motion работает только при prefers-reduced-motion: no-preference.
Монохром и один акцент
В базе сайт монохромный, чтобы на первом плане был контент. Оранжевый работает как акцент и добавляет «жизни»: он появляется в поле глифов под курсором, в частицах сферы, в прелоадере и в выделении текста.
Стек и то, что не взял
- Astro, статическая сборка, коллекции контента со схемами. Каждый кейс, проект и заметка это папка с
meta.ymlи одним текстовым файлом на язык. Сборка падает, если у кейса или проекта нет пары языков. - Нативный CSS:
@layer, custom properties, вложенность, container queries. - Motion, vanilla-сборка движка, на котором стоит Framer Motion, поэтому пружины и кривые совпадают со старым сайтом.
- Lenis для плавного скролла длительностью 0,8 с. На тач-экранах и при reduced motion он выключен.
- three.js для одной сцены.
- Playwright для тестов.

Три лаборатории, чтобы настраивать глазами
Самое сложное в работе с ИИ это финальная полировка, особенно для дизайнера, которому важно, как вещь выглядит и ощущается. Поэтому вместе с Claude я собрал три лаборатории: для курсора, для прелоадера и для 3D-сферы. Каждое значение я настроил на глаз, отдал результат агенту, и он внедрил ровно так. Обычный вайб-кодинг заканчивается там, где закончил агент. У меня есть fine tune, который я полностью контролирую, поэтому готовый продукт выглядит так, как нужно мне.
Ещё сильнее в полировке помог самописный интерфейс для ревью. Прямо поверх живого сайта я оставляю точечные заметки к любому элементу, а агент получает их вместе с адресом элемента на странице. Не нужно описывать в чате, где именно лежит кнопка или абзац, поэтому до идеала можно доводить любую мелочь.
Вид сферы, курсора и прелоадера лежит не в коде. У каждого есть лаборатория, отдельная страница, которая запускает настоящий модуль, и значения я настраиваю там ползунками.
В лаборатории сферы 41 фигура, она задаёт фигуру для каждой страницы, кейса и проекта, а ещё наклон, вращение, число и размер частиц, физику курсора, цвета по темам и скорость морфинга. В лаборатории курсора 29 типов элементов от кнопок до слайдов, и каждому типу достаётся одна из семи реакций: none, halo, orbit, spotlight, underline, corners или ripple. Лаборатория прелоадера задаёт ритм 9 ударов, изинги и финал.
Каждая лаборатория экспортирует JSON-файл, а скрипт его проверяет и пишет конфиг, который читает сайт. Так лаборатория остаётся источником правды, и конфиг руками никто не правит. В лаборатории курсора есть ещё закреплённый режим: курсор сам встаёт на выбранный элемент, поэтому ползунки можно двигать, не держа там мышь.


Сфера и курсорное поле
Сфера по умолчанию состоит из 1600 частиц на three.js Points с шейдерным спрайтом. Физика считается на CPU, а рендер спит, когда ничего не движется и когда сфера вне экрана. three.js весит около 131 КБ gzip и грузится динамическим import() только когда сфера видна. Запасной вариант это статичный кадр AVIF, снятый с той же сцены.

Курсорное поле это порт компонента Framer на Canvas 2D, без библиотек. Его цикл засыпает, когда курсор стоит больше 0,7 секунды и все точки погасли. След лежит под стеклянными островами. Свечение, волны и орбиты наведённого элемента рисуются на холстах поверх контента, потому что под стеклом они выходили приглушёнными и размытыми.
Навигация, которая сохраняет энергию
Сначала я использовал Astro ClientRouter, а холст поля и сфера переживали переходы между страницами, чтобы не перезапускаться. Позже я заменил компонент его мета-тегами и собственным router-init.ts, чтобы код роутера грузился после первой отрисовки. Переход страницы занимает 0,2 с на уход и 0,2 с на появление, а обложка кейса перетекает в hero следующей страницы.
Производительность: LCP с 2,6 до 1,66 секунды
Первый замер Lighthouse mobile дал LCP 2,6 с на главной и 2,7–3,0 с на кейсе при цели 1,8 с. Причины: 20+ JavaScript-модулей, кириллический шрифт, который грузился заранее на английской версии, скрытый вариант картинки для светлой или тёмной темы, который грузился eager, и отдельные запросы за заглушками картинок.
Что исправил:
- весь клиентский код стартует после события
loadи кадра LCP (boot.ts); - файлы шрифтов сделаны сабсетами: латиница 33,8 → 23,1 КБ, кириллица 15,9 → 10,9 КБ;
- картинка с парой для светлой и тёмной темы грузит только нужную тему;
- стрелки сделаны SVG, поэтому отдельный сабсет шрифта для символов больше не нужен.
10 октября 2026 года у главной LCP был 1,66 с, у страницы кейса около 2,0 с (Lighthouse mobile с симулированным троттлингом, медиана из 3 запусков, production preview). Performance был 98–99, а accessibility, best practices и SEO по 100. CLS и TBT были 0.

Прелоадер
При первом визите за сессию собирается знак, затем 9 ударов переключают кадры знака в разных материалах, а финал зумится в центральный шар и садится в логотип в навигации. Любой ввод пропускает его.
В первой версии кадры лежали в DOM как картинки, и браузер считал их кандидатами в LCP: 3 с на троттлинг-профиле телефона против 0,17 с без прелоадера. Теперь модуль отдаёт кадры как canvas, и LCP снова 0,18–0,32 с.

Что пошло не так
Кнопки шапки ломались после переходов между страницами: обе иконки темы показывались слева. Два компонента объявляли @layer components в собственных стилях. После перехода Astro вставлял эти стили заново, строка с порядком слоёв для всего сайта оказывалась после них, и components попадал ниже reset. Простое правило svg { display: block } из reset начинало перебивать правило, которое прячет луну или солнце. Исправление: отдельный inline-стиль с порядком слоёв сразу после скрипта темы, который роутер оставляет на месте. Теперь на это есть тест.
Сфера перестала работать на моей машине по другой причине. npm run build писал в тот же node_modules/.vite, что и запущенный dev-сервер, и ломал его оптимизированные зависимости, поэтому three.js отдавал 504 и оставалась только статичная картинка. Теперь у сборки свой каталог кеша.
На что обратить внимание
- Safari пока не проверен, и AVIF с альфой в кадрах прелоадера это первое, на что я там посмотрю.
- Курсорное поле засыпает, когда курсор стоит, чтобы экономить батарею. На старом сайте на Framer пятно под неподвижным курсором горело постоянно, и с этой разницей я решил жить.
