Ambience buro
Сайт архитектурно-дизайнерского бюро полного цикла
Роль
Frontend/Fullstack-разработчик (соло, полный цикл)
Год
2021
Стек
FigmaHTMLSCSSJavaScriptGulpWebpackGoogle Maps JS APIPHPWordPress

Задача
Бюро полного цикла: архитектура домов, интерьеры квартир и коммерческих помещений, частные и корпоративные заказчики. На входе были бриф и логотип — ни сайта-предшественника, ни дизайн-системы.
Ключевое требование сформулировал сам заказчик: за счёт монохромной схемы выделить реализованные проекты. Интерфейс не должен спорить с фотографиями объектов — аккуратные линии, тонкая типографика, минимум декора. Плюс к этому: портфолио, где объект можно оценить не открывая карточку; шаблон проекта с планировками и галереями; три страницы услуг; блог, рассчитанный на E-E-A-T. И всё это — на WordPress, чтобы бюро вело портфолио и статьи самостоятельно, без разработчика.
Этапы
Аналитика и упаковка брифа
- Разбор постраничного брифа: целевая аудитория, УТП, пять сайтов-референсов с разбором «что именно понравилось»
- Проектирование карты сайта: главная, портфолио, страница проекта, три страницы услуг, о компании, блог, контакты
- Перевод пожеланий заказчика в технические требования — например, «пусть шапка меняет цвет на противоположный блоку» стало отдельным модулем на ScrollTrigger
Дизайн-концепция в Figma
- Монохромная схема, тонкая типографика: Manrope 200 в наборном тексте, Ysabeau SC в заголовках
- Минимум графических элементов — визуальный вес осознанно отдан фотографиям объектов
- Ширина контейнера пересмотрена по итогам согласования с 1690 до 1240 px: макет стал плотнее, а кадры проектов — крупнее относительно полей
Вёрстка и сборка
- Собственный пайплайн: Gulp оркестрирует Webpack, дальше пост-обработка ассетов
- Mobile-first как жёсткое правило, а не как намерение
- HTML собирается из партиалов, шесть страниц — чистая композиция блоков
Анимации и интерактив
- GSAP ScrollTrigger: контекстная шапка, pin-стек услуг, закрепление блоков преимуществ
- Swiper: карточка портфолио с перелистыванием кадров по наведению
- lightGallery в связке с justified-раскладкой для галерей проекта
- Click-to-load фасад для YouTube, две карты Google, перенос DOM-узлов в мобильное меню
Интеграция в WordPress
- Контентная модель на ACF: поля проекта, поля статьи, страница опций
- 12 шаблонов страниц поверх общей шапки и подвала
- AJAX-фильтрация портфолио и блога с сохранением состояния в адресной строке
- Формы на Contact Form 7 с целью в Яндекс.Метрике на успешную отправку
SEO, E-E-A-T и запуск
- JSON-LD организации и
FAQPage, генерируемый из контентного поля - Блок автора статьи, «Читайте также», хлебные крошки во всех разделах
- Собственная плашка cookie-consent
- Метатеги
Процесс
Реализация
Дизайн-система в SCSS
Токены собраны в одной точке: $mainFont / $titleFont, палитра #000 / #fff / #747474, $maxWidthContainer: 1240 плюс 30 px гаттеров.
Контейнер реализован атрибутным селектором [class*='__container']: любой блок, назвав свой внутренний элемент по BEM-соглашению, автоматически получает сетку и поля — без утилити-класса в разметке и без миксина в каждом файле.
Mobile-first без исключений
295 медиазапросов min-width против 8 max-width — база написана под мобильный, десктоп добирается сверху. Шкала трёхступенчатая: база (< 768) → ≥ 768 → ≥ 992, без промежуточных костылей.
Все состояния :hover завёрнуты в @media (any-hover: hover), поэтому на тач-устройствах наведение не залипает после тапа — частая болячка каталогов с крупными карточками.
Карточка проекта с перелистыванием кадров
Главная интерактивная идея портфолио: карточка должна показывать объект, а не одну обложку.
Внутри каждой карточки — Swiper с effect: 'fade'. На десктопе автоплей останавливается прямо в обработчике init, дальше слайдером управляет только указатель: mouseenter выставляет задержку 100 мс и запускает прокрутку, slideChange поднимает задержку до 800 мс, mouseleave останавливает. За счёт этого первый кадр сменяется почти мгновенно — пользователь сразу получает отклик, — а дальше кадры идут спокойным ритмом, не превращаясь в мигание. На тач-устройствах ветка инвертируется: наведения нет, поэтому автоплей идёт сам.
Кадры не заводятся руками: это обложка записи плюс первые три изображения галереи проекта. Контент-менеджер наполняет одну галерею, а превью в трёх местах сайта собирается из неё автоматически.
Контекстная шапка на ScrollTrigger
Секции размечены классами _dark / _light / _transparent. Для каждой создаётся ScrollTrigger, у которого границы сдвинуты на высоту шапки (start: 'top-=${headerHeight} top') — переключение происходит ровно в момент, когда шапка физически заходит на новый блок, а не когда его верх достигает вьюпорта. Шапка меняет тему на контрастную блоку, переход плавный.
Поверх этого — независимое скролл-поведение: фон появляется после ухода с первого экрана, шапка прячется при движении вниз и возвращается при движении вверх. Оба механизма читают позицию через один общий слушатель прокрутки, а не заводят свой.
Pin-стек услуг
Каждая карточка услуги закрепляется на высоту экрана и кроссфейдится по opacity, привязанной к прокрутке через scrub, — карточки наезжают друг на друга стопкой. Первый и последний элементы обрабатываются отдельными ветками, чтобы стек корректно начинался и корректно отпускал страницу на выходе.
Галерея проекта
У проекта три независимые галереи с собственными подписями — обычно это визуализации, фотографии реализации и детали. Любую можно оставить пустой, шаблон это переживает.
Раскладка — justified-сетка: строки выравниваются по высоте (532 px на десктопе, 400 на мобильных), при этом исходные пропорции кадров сохраняются. Для интерьерной съёмки, где вперемешку идут горизонтали, вертикали и панорамы, это работает лучше жёсткой сетки — ничего не обрезается по центру.
Просмотр — lightGallery, собранный только с двумя нужными плагинами (миниатюры и полноэкранный режим); остальные модули библиотеки просто не импортируются и в бандл не попадают.
Контентная модель на ACF
Проект описывается полями: город, год, площадь, теглайн, подробное описание, изображение планировки и три галереи с подписями. Статья — превью-текстом и репитером FAQ. Телефон вынесен на страницу опций, чтобы менялся в одном месте.
Теги записей используются как ось фильтрации портфолио — тип объекта (квартиры, дома, коммерческая недвижимость). Список фильтров нигде не захардкожен: он собирается из тегов реально опубликованных проектов, поэтому появление нового типа объекта не требует правки кода.
AJAX-фильтрация с URL-состоянием
Два фильтра — портфолио по типам объектов и блог по подкатегориям статей. Обработчики на стороне WordPress отдают готовую разметку карточек, клиент подменяет содержимое списка и показывает лоадер на время запроса.
Главное здесь — состояние живёт в адресной строке. history.pushState пишет ?tag=, поэтому ссылку на выборку «Квартиры» можно отправить, и она откроется уже отфильтрованной: при холодной загрузке параметр читается из URLSearchParams, подсвечивает нужный чип и повторяет запрос. Фильтр перестаёт быть состоянием одной вкладки и становится адресуемым.
Мост между AJAX и слайдерами
Внутри каждой подгруженной карточки — собственный Swiper, который нужно инициализировать после вставки в DOM. Вместо прямого вызова из обработчика запроса код бросает пользовательское DOM-событие ajaxContentLoaded, на которое подписан модуль слайдеров.
В результате слой запросов и слой Webpack-модулей не знают друг о друге: фильтр не импортирует Swiper, слайдеры не знают о существовании фильтра. Добавить ещё одного подписчика на «контент обновился» — одна строка, без правок в фильтре.
Ленивая загрузка превью
Собственный IntersectionObserver для сетки портфолио: в src карточки лежит лёгкая миниатюра, полноразмерное изображение — в data-src и подставляется при приближении к вьюпорту. Первый экран каталога грузится миниатюрами, полные кадры подтягиваются по мере прокрутки. Есть ветка для браузеров без IntersectionObserver — там подмена происходит сразу.
E-E-A-T в шаблоне статьи
Блок автора собирается из профиля WordPress: аватар, имя, роль — сигнал авторства для поисковых систем и просто честная подпись под материалом.
FAQ живёт в одном ACF-репитере и рендерится дважды: как аккордеон для читателя и как JSON-LD FAQPage для поисковика. Разметка формируется через json_encode с флагом JSON_UNESCAPED_UNICODE, чтобы кириллица не превращалась в \u-последовательности. Редактор заполняет вопросы один раз — микроразметка появляется сама.
Блок «Читайте также» подбирает материалы по общим рубрикам, а анонс режется по границе предложения регулярным выражением, а не по количеству символов. Мелочь, но она убирает обрывы на полуслове, которые выдают шаблонный сайт.
Карты
Два инстанса Google Maps JS API: карта присутствия с метками по семи городам и своим уровнем зума для мобильных, и карта офиса на странице контактов. Оформление вынесено в облачный mapId, поэтому стили карты меняются без пересборки фронтенда. Метки — AdvancedMarkerElement с собственным SVG вместо дефолтной булавки.
Инициализация происходит только если контейнер карты есть на странице, библиотеки подтягиваются через Promise.all(importLibrary(...)) — на страницах без карты Google Maps не загружается вообще.
Результаты
шесть типов страниц статики развёрнуты в двенадцать шаблонов WordPress поверх общей шапки и подвала
SCSS-блоков в поблочной архитектуре
AJAX-фильтра с состоянием в URL — ссылка на выборку шарится и открывается готовой