Главная
Все проекты

Ambience buro

Сайт архитектурно-дизайнерского бюро полного цикла

Роль

Frontend/Fullstack-разработчик (соло, полный цикл)

Год

2021

Стек

FigmaHTMLSCSSJavaScriptGulpWebpackGoogle Maps JS APIPHPWordPress

Ambience buro

Задача

Бюро полного цикла: архитектура домов, интерьеры квартир и коммерческих помещений, частные и корпоративные заказчики. На входе были бриф и логотип — ни сайта-предшественника, ни дизайн-системы.

Ключевое требование сформулировал сам заказчик: за счёт монохромной схемы выделить реализованные проекты. Интерфейс не должен спорить с фотографиями объектов — аккуратные линии, тонкая типографика, минимум декора. Плюс к этому: портфолио, где объект можно оценить не открывая карточку; шаблон проекта с планировками и галереями; три страницы услуг; блог, рассчитанный на E-E-A-T. И всё это — на WordPress, чтобы бюро вело портфолио и статьи самостоятельно, без разработчика.

Этапы

01

Аналитика и упаковка брифа

  • Разбор постраничного брифа: целевая аудитория, УТП, пять сайтов-референсов с разбором «что именно понравилось»
  • Проектирование карты сайта: главная, портфолио, страница проекта, три страницы услуг, о компании, блог, контакты
  • Перевод пожеланий заказчика в технические требования — например, «пусть шапка меняет цвет на противоположный блоку» стало отдельным модулем на ScrollTrigger
02

Дизайн-концепция в Figma

  • Монохромная схема, тонкая типографика: Manrope 200 в наборном тексте, Ysabeau SC в заголовках
  • Минимум графических элементов — визуальный вес осознанно отдан фотографиям объектов
  • Ширина контейнера пересмотрена по итогам согласования с 1690 до 1240 px: макет стал плотнее, а кадры проектов — крупнее относительно полей
03

Вёрстка и сборка

  • Собственный пайплайн: Gulp оркестрирует Webpack, дальше пост-обработка ассетов
  • Mobile-first как жёсткое правило, а не как намерение
  • HTML собирается из партиалов, шесть страниц — чистая композиция блоков
04

Анимации и интерактив

  • GSAP ScrollTrigger: контекстная шапка, pin-стек услуг, закрепление блоков преимуществ
  • Swiper: карточка портфолио с перелистыванием кадров по наведению
  • lightGallery в связке с justified-раскладкой для галерей проекта
  • Click-to-load фасад для YouTube, две карты Google, перенос DOM-узлов в мобильное меню
05

Интеграция в WordPress

  • Контентная модель на ACF: поля проекта, поля статьи, страница опций
  • 12 шаблонов страниц поверх общей шапки и подвала
  • AJAX-фильтрация портфолио и блога с сохранением состояния в адресной строке
  • Формы на Contact Form 7 с целью в Яндекс.Метрике на успешную отправку
06

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 не загружается вообще.

Результаты

6 → 12

шесть типов страниц статики развёрнуты в двенадцать шаблонов WordPress поверх общей шапки и подвала

39

SCSS-блоков в поблочной архитектуре

2

AJAX-фильтра с состоянием в URL — ссылка на выборку шарится и открывается готовой

Экраны