Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Париста · Boutique

Лендинг бутика табака и вейпинга. Ультраминималистичный люкс: обсидиан, платина и ни одного лишнего цвета.

Next.js TypeScript React CSS Modules


Демонстрационная работа. Магазин, тексты и контакты вымышлены. Сайт не имеет юридической силы и сделан как кейс в портфолио.

Что это

Второй кейс на одном бренде. Первый, H:\parista, сделан в языке тёмного неонового глянца: бирюза, стекло, дым, фотографии витрины. Этот собран с нуля в противоположной эстетике нишевого бутика, и общих элементов у них нет намеренно.

Неоновый кейс Этот
Акцент бирюза #00E5FF платина #E6DFD5
Заголовки Cormorant Garamond, антиква Unbounded Light, архитектурный гротеск
Материал стекло с размытием и цветной гранью матовая панель с волосяной металлической гранью
Скругления 18–24 px 2 px
Фон дым на холсте, точечная сетка микро-радиальный свет и зерно
Секции чередование тёмных полос один обсидиан, разделённый волосяными линиями
Появление плавный подъём штора по маске clip-path
Каталог сетка карточек с фото бенто без зазоров, блик за курсором

Возможности

  • Живой статус работы в шапке и в подвале: считается по часам покупателя, обновляется раз в минуту, точка пульсирует вторым кольцом
  • Бенто-каталог на шесть разделов без зазоров: плитки разделены самой линией сетки, как швы на корпусе. При наведении описание выезжает снизу, крупный номер уходит глубже, за курсором идёт мягкий платиновый блик
  • Мессенджеры реальными ссылками: MAX, WhatsApp с готовым текстом, Telegram
  • Подтверждение возраста первым экраном и куки-полоса после него
  • Заголовки открываются шторой по clip-path, блоки поднимаются при прокрутке
  • Всё движение отключается по prefers-reduced-motion
  • Адаптив от 320 px
  • Заголовки безопасности: CSP от default-src 'none', frame-src 'none', HSTS, nosniff. Чужих доменов на сайте нет вообще
  • Разметка schema.org/TobaccoShop с часами, контактами и каталогом

Первый экран

Пара здесь нет намеренно: он уже работает на неоновом кейсе, и повтор убил бы то самое отличие, ради которого этот сайт и делался. Вместо частиц взята физика материала.

Металл в заголовке. Буквы залиты не краской, а широким градиентом от тёмной платины к белому и обратно. Узкий блик ходит по ним за курсором, без курсора продолжает ходить сам. Заливка висит на самой строке, а не на заголовке: если поднять её выше, маска открытия перестанет работать, потому что она обрезает элемент, а фон рисовался бы уровнем выше.

Есть @supports на background-clip: text. Где его нет, буквы просто остаются платиновыми.

Световая полоса. Очень широкая и очень слабая, идёт по всей плоскости за тем же курсором. Читается как лампа, проезжающая над витриной.

Знак за текстом. Фирменный знак ростом в 900 пикселей, непрозрачность 3%, уходит за правый край. Сносится в сторону, обратную курсору: получаются две плоскости вместо одной, и появляется глубина без единой фотографии.

Всё это один цикл requestAnimationFrame на весь экран. Значения не присваиваются напрямую, а догоняются: резкий блик выдаёт скрипт, плавный читается как отражение. Курсор пропал на две секунды, и цели начинают ходить по синусу сами, чтобы экран не замирал.

По prefers-reduced-motion цикл не запускается вовсе, полоса и знак скрываются, металл остаётся неподвижным.

Поверхность

Текстура лежит на всей странице одним закреплённым слоем: мелкое зерно плюс очень редкая диагональная штриховка. Вместе они читаются как брашированный металл под краской, по отдельности почти незаметны.

Слой закреплён по экрану, а не по документу. Поверхность стоит на месте, пока контент едет мимо, и страница ощущается лежащей за стеклом. Уровень подобран так, чтобы окно 18+ и куки остались над ним чистыми.

Ни одной картинки: зерно нарисовано фильтром прямо в data: URI, штриховка это повторяющийся градиент.

Шкала разделов

Слева висит оглавление из пяти пунктов. Показывает, где человек сейчас, и даёт прыгнуть в любой раздел. Активная позиция подсвечена бронзой, название разворачивается по наведению.

Активным считается раздел, чей верх последним прошёл треть экрана. IntersectionObserver тут не годится: на длинных секциях в поле зрения их сразу две, и он честно рапортует про обе. Поэтому меряем сами, но не чаще одного раза в кадр.

Шкала появляется только после первого экрана и прячется, когда окно уже и места слева не остаётся.

Как добраться

Карта грузится сама, метка стоит по координатам. Поиск по строке адреса Яндекс понимает вольно и подсовывает соседние организации, поэтому передаём готовую точку. Порядок в параметрах у него долгота, потом широта.

Виджет приходит светлым и на обсидиане выглядит дырой в стене. Он проходит через инверсию с поворотом тона: подложка уходит в тёмное, подписи становятся светлыми. Насыщенность душится почти в ноль, остаток уводится в тёплое, иначе после инверсии красные значки Яндекса становятся бирюзовыми, а бирюза здесь запрещена.

Рядом лежит шкала суток. Светлая часть это рабочие часы, метка показывает текущий момент и подсвечивается бронзой, пока магазин открыт.

Палитра

Роль Цвет
Фон обсидиан #0A0C0E
Панель #0C0E11#101317 градиентом
Панель под курсором #151920
Текст платина #E6DFD5
Второстепенный платина 70% и 45%
Линии платина 9% и 16%
Номера разделов, точка статуса бронза #B5936A

Бирюзы нет ни в одном месте: она осталась в прошлом кейсе.

Типографика

Заголовки: Unbounded, начертания 200 и 300. В брифе стояли Syne, Cabinet Grotesk и Monument Extended, но у первого на Google Fonts только латиница, второй живёт на Fontshare, третий коммерческий. На русском заголовке любой из них отвалился бы на системный шрифт. Unbounded это ближайший живой аналог Monument Extended с полной кириллицей.

Текст: Inter. Оба шрифта вшиваются при сборке, к Google браузер не обращается ни разу.

Капса в заголовках нет: широкий гротеск в верхнем регистре мгновенно превращается в вывеску. Верхний регистр остался только в подписях разделов и там работает подписью, а не заголовком.

Структура

parista-lux/
├── src/
│   ├── app/
│   │   ├── layout.tsx       шрифты, метаданные, восстановление состояния
│   │   ├── page.tsx         сборка страницы и разметка для поисковиков
│   │   ├── globals.css      токены, типографика, панель, кнопки
│   │   └── icon.svg         фавикон
│   │
│   ├── components/
│   │   ├── Header.tsx       шапка со статусом работы
│   │   ├── Status.tsx       живой индикатор «открыто до 23:00»
│   │   ├── Hero.tsx         первый экран, свет и зерно
│   │   ├── Manifest.tsx     блок «кто мы»
│   │   ├── Catalog.tsx      бенто-сетка с бликом за курсором
│   │   ├── Why.tsx          три причины
│   │   ├── Footer.tsx       связь, реквизиты, дисклеймеры
│   │   ├── Gate.tsx         окно 18+ и куки
│   │   ├── Reveal.tsx       появление при прокрутке
│   │   └── Icons.tsx        значки одной линией
│   │
│   └── lib/content.ts       тексты, контакты, состав каталога
│
├── next.config.ts           заголовки безопасности
└── dev.bat / start.bat      запуск без Node в системном PATH

Что где править

Задача Файл
Телефон, адрес, часы работы src/lib/content.tsshop
Ссылки на мессенджеры src/lib/content.tslinks
Разделы каталога и размер плиток src/lib/content.tscatalog
Три причины src/lib/content.tswhy
Тексты окна 18+ и куки src/lib/content.tsui
Цвета, шрифты, материал src/app/globals.css

Размер плитки задаётся полем size: hero занимает две колонки и два ряда, wide две колонки, unit одну. Порядок в массиве и есть порядок на сетке.

Логотип

Знак пришёл фотографией подсвеченной вывески с прилавка: белое свечение на серо-синем фоне. Просто обрезать такой снимок нельзя, поэтому он обведён в кривые.

Как это сделано. Снимок увеличен вчетверо, порезан порогом по яркости (разрыв между знаком и фоном там в полторы сотни уровней, так что порог устойчив), связные области размечены обходом в ширину, границы обведены по Муру, ступеньки спрямлены Рамером и Дугласом, углы срезаны Чайкиным и переписаны кубическими кривыми. Получилось четыре контура и 5.6 КБ пути.

Файл Что это
src/components/Mark.tsx знак в разметке, красится через currentColor
src/app/icon.svg вкладка браузера: знак на обсидиановой плашке
src/app/favicon.ico семь размеров в одном файле, от 16 до 256
src/app/apple-icon.png 180, для домашнего экрана
public/logo.svg чистый вектор без плашки
public/logo-white.png белый знак на прозрачном, 1024

У мелких размеров иконки поле меньше, а скругление плашки слабее: на 16 пикселях каждый лишний пиксель отступа съедает штрих знака.

Фотографии

Слоты уже впаяны и ждут файлов. Пока файла нет, плитку держат крупный номер, свет и грани, а широкая полоса в блоке «Кто мы» не рисуется вовсе. Положили файл, он проявился. Ни скачков, ни битых рамок: макет одинаков в обоих состояниях.

Формат везде JPEG, цветовой профиль sRGB, качество 80–85.

Каталог, шесть кадров

Кладутся в public/img/catalog/. Имя файла совпадает с id раздела в content.ts, менять его нельзя.

Файл Раздел Размер Что в кадре
shisha-tobacco.jpg Табак для кальяна 1800 × 1600 стопка банок под углом, одна открыта
hookahs.jpg Кальяны 1600 × 1000 один аппарат целиком, силуэтом
pods.jpg POD-системы 1000 × 1000 два-три устройства, одно в руке
disposables.jpg Одноразки 1000 × 1000 ряд корпусов торцом, уходящий в расфокус
flavours.jpg Ароматизаторы 1600 × 1000 флаконы строем, свет сзади через стекло
parts.jpg Комплектующие 1600 × 1000 чаша, калауд и уплотнители россыпью

Остальное

Файл Путь Размер Что в кадре
interior.jpg public/img/ 2400 × 1030 (21:9) зал во всю ширину, витрины по бокам
og.jpg public/ 1200 × 630 знак и название на обсидиане, для соцсетей

Как снимать

Фон однотонный тёмный, ближе к обсидиану, чем к чёрному. Свет мягкий боковой или контровой, одна ключевая точка плюс отражатель. Никакого цветного неона: он подерётся с платиной и уронит всю концепцию.

Кадр строим с воздухом, предмет мельче, чем хочется: снимок в плитке приглушён до 34% и перекрыт затемнением снизу, крупный объект в упор превратится в пятно. Композицию держим по центру или чуть выше, низ кадра уходит под подпись.

Вертикальные кадры не подойдут: все слоты горизонтальные или квадратные.

Если хотите WebP

Он весит примерно вдвое меньше. Расширение задано в двух местах, поменять надо оба: src/components/Catalog.tsx (строка со Shot) и src/components/Manifest.tsx (строка с Band).

Запуск

Node.js 18 или новее. Сайт поднимается на порту 3100, чтобы не спорить с соседним проектом на 3000.

npm install && npm run dev

Боевая сборка:

npm run build && npm start

На Windows без Node в PATH используйте ярлыки dev.bat и start.bat.

Сборка

Route (app)                     Size     First Load JS
┌ ○ /                         4.67 kB       107 kB
├ ○ /_not-found                 993 B       104 kB
└ ○ /icon.svg                     0 B          0 B

○  (Static)  prerendered as static content

Зависимостей ровно три: next, react, react-dom. UI-библиотек, Tailwind и анимационных пакетов нет, всё поведение написано вручную.

Известные ограничения

  • Предметной съёмки нет, каталог держится на типографике
  • Прайса нет намеренно: бутик показывает ассортимент, а не ценник
  • Смены тем нет: концепция построена на одном обсидиане, светлый вариант размыл бы её
  • Ссылка на MAX ведёт по номеру телефона, а не в конкретный чат

Деплой

Разворачивается на Vercel без настроек. После деплоя впишите выданный адрес в SITE_URL в src/lib/content.ts.

Лицензия

MIT

About

Веб-сайт табачного магазина.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages