Skip to content

Latest commit

 

History

History
210 lines (163 loc) · 13.1 KB

File metadata and controls

210 lines (163 loc) · 13.1 KB

Отступы и ритм — источник правды

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

Читать перед добавлением нового блока и перед любой правкой py-*, mt-*, mb-*, gap-* в разметке.


1. Принцип

Отступ — это роль, а не число. Ролей пять по вертикали и две по горизонтали. Каждая роль — один токен; своего clamp() в разметке быть не должно.

До введения шкалы вертикальный ритм секций был набран четырьмя разными формулами (clamp(3rem,6vw,8rem), clamp(3.5rem,7vw,7rem), clamp(4rem,7vw,9rem), фиксированные mb-10). Кривые пересекались: на ширине ~1000px «маленькая» секция имела отступ больше «большой», а на 1600px — наоборот. Порядок значений менялся вместе с шириной экрана — это и есть отсутствие системы, а не набор случайных чисел.

2. Шкала

Токены живут в блоке @theme в ../src/styles/global.css. Неймспейс --spacing-* принадлежит Tailwind, поэтому именованный ключ даёт сразу и var(--spacing-block) в CSS, и утилиты mt-block / mb-block / gap-block / p-block в разметке.

Токен Формула 390px 1440px 1920px Роль
--spacing-section clamp(3rem, 6vw, 8rem) 48 86 115 Внешний ритм секции на светлом фоне
--spacing-section-lg clamp(4rem, 7vw, 9rem) 64 101 134 То же для секции поверх фотопанорамы
--spacing-block clamp(2.5rem, 5vw, 4.5rem) 40 72 72 Шапка секции → контент; крупные блоки внутри секции
--spacing-group clamp(2rem, 3.5vw, 3rem) 32 48 48 Подраздел внутри блока
--spacing-gutter clamp(2.5rem, 4vw, 5rem) 40 58 77 Зазор между колонками grid'а
--spacing-panel clamp(1.75rem, 3vw, 3rem) 28 43 48 Внутренние поля крупной панели (врезка с цитатой, карточки модалок)

Ниже ступени group работает обычная шкала Tailwind (mt-2mb-8): она уже кратна 4px и в токенах не нуждается. Токен нужен там, где расстояние должно расти вместе с экраном.

--spacing-panel стоит особняком: это не ступень ритма страницы, а «толщина стенки» контейнера.

Почему две ступени для секций

section-lg получают секции, лежащие поверх фотографии (#video, #offer-stats, #request). Причина не в важности блока, а в фоне: на снимке контент без запаса поля читается как наклейка поверх картинки. Правило механическое — фотофон есть, значит ступень lg; фона нет, значит базовая.

3. Как применять

Вертикаль секции

<section class="relative z-10 bg-bg section-py"></section>       <!-- светлая -->
<section class="relative z-10 … section-py-lg"></section>        <!-- на фото -->

.section-py и .section-py-lg — composition-классы в @layer components, внутри это padding-block: var(--spacing-section[-lg]). Нужна асимметрия (pt без pb) — берутся утилиты: pt-section, pb-section-lg.

Горизонталь

<div class="section-px"></div>        <!-- px-6 sm:px-10 md:px-12 lg:px-20 -->
<div class="section-px-wide"></div>   <!-- px-6 sm:px-10 md:px-12          -->

Ступени всего две. section-px-wide — для контента, который живёт «ковром» во всю ширину и на lg+ отдаёт поля ради размера: диаграмма ПРП, лента логотипов. Всё остальное — section-px.

Отдельный edge-случай — masonry-лента галереи: её боковые поля равны зазору между кадрами (--gallery-gap, 4–8px), чтобы решётка читалась единым полем. Это задокументированное исключение, третьей ступенью полей оно не является.

Внутри секции

<div data-reveal>
  <p class="t-eyebrow t-on-muted mb-6">Надзаголовок</p>   <!-- eyebrow → H2: всегда mb-6 -->
  <h2 class="t-h-section t-on-brand">Заголовок</h2>
</div>

<div class="mt-block">…контент секции…</div>              <!-- шапка → контент -->
<div class="mt-group">…следующий подраздел…</div>          <!-- внутри блока -->
<div class="grid lg:grid-cols-12 gap-gutter"></div>       <!-- колонки -->

Модалки

Внутренние поля контента модалки — всегда ступень panel, независимо от того, что внутри: лонгрид, список, цитата или расшифровка видео.

Слой Отступы
<dialog> → карточка --dialog-py / --dialog-px (поля модального слоя)
Карточка → контент p-panel (или px-panel + своя вертикаль)
Полоса-шапка карточки px-panel + вертикаль по Tailwind-шкале (py-3, py-5)

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

Лайтбоксы #modal-certificate и #modal-gallery в это правило не входят: внутри них изображение во весь экран, полей у контента нет вовсе.

Микрорасстояния

Устойчивые микрорасстояния, которые токенами не покрываются и держатся Tailwind-шкалой:

Пара Отступ
eyebrow → H2 (и eyebrow → список) mb-6
H2 → лид mt-8
лид → уточняющий абзац mt-4
текст → подпись/figcaption mt-6

4. Карта страницы

Секция Вертикаль Горизонталь
Hero свой контракт первого экрана (pt-10 md:pt-14, pb-[55vh] md:pb-14) section-px
#about-training section-py section-px
#video section-py-lg section-px
#examples pt-section + pb-0 section-px
#offer-stats section-py-lg section-px
#prp-steps section-py, xl:pb — половина ступени section-px-wide
#about-trainer section-py на контентной колонке section-px
#certificate section-py section-px
#clients section-py section-px + section-px-wide у ковра логотипов
#reviews section-py section-px
#training-photos section-py section-px + edge-поля у masonry
#request section-py-lg section-px
#modal-about, #modal-examples, #modal-review, #modal-video-review вертикаль контента — panel, у #modal-about низ — block panel
#modal-certificate, #modal-gallery полей нет: изображение во весь экран

5. Задокументированные исключения

Их четыре, каждое с комментарием прямо в коде:

  1. #examples, pb-0. Фото Дениса стоит на нижней границе секции (lg:self-end); нижний паддинг оторвал бы фигуру от края. Текстовая колонка компенсирует это своим lg:pb-block — иначе подсказка под списком категорий прилипает вплотную к следующей секции.
  2. #prp-steps, xl:pb-[calc(var(--spacing-section)/2)]. У stage-«креста» (aspect 13/10) под октагоном 5 есть встроенная пустота ~4% ширины stage. Полная ступень поверх неё делала низ заметно толще верха. Считается от токена, а не своим clamp(): сдвинется шкала — сдвинется и он.
  3. #about-training, gap-x колонки с иероглифом (clamp(2.5rem,6vw,8rem)) шире ступени gutter намеренно: эта колонка работает как поле-разделитель, а не как соседняя колонка контента.
  4. Hero. Первый экран живёт по своему контракту (min-h-screen, резерв ~55vh под фото на мобильном) и в ритм страницы не входит.

Конца страницы «воздушным» <div> нет: полоса белого под тёмной формой читалась как обрыв вёрстки, а собственный pb у #request финалу воздуха уже даёт. Появится футер — встанет сразу после <LeadForm />.

6. Анти-паттерны

  • Свой clamp() для отступа в разметке. Любое расстояние, которое растёт с экраном, — это одна из шести ролей. Не подошла ни одна — сначала спроси, правда ли нужна седьмая.
  • Фиксированный py-16 / mb-10 на границе секции или блока. Секция обязана дышать вместе с вьюпортом, фикс из Tailwind-шкалы этого не делает.
  • Новая ступень «чуть больше существующей» (clamp(2.5rem,5vw,5rem) рядом с clamp(2.5rem,5vw,4.5rem)). Разница в 8px не читается, а словарь удваивается.
  • Повторённая формула вместо переменной. Поля модального слоя (--dialog-py/--dialog-px) участвуют ещё и в расчётах max-height карточек: разъехавшись с padding'ом, значение молча обрежет содержимое.
  • Асимметричный py без причины. ptpb — только там, где к границе секции что-то примыкает (фото в край, встроенная пустота диаграммы), и обязательно с комментарием.
  • Элемент, торчащий за поля секции. На мобильном он растягивает горизонтальную прокрутку всей страницы. Проверять надо computed-геометрией, а не глазами: visibility: hidden из потока не убирает (на этом уже попадалась подсказка .termtip__bubble).

7. Контрольные проверки

Свои clamp() в отступах разметки — должно остаться ровно одно попадание, задокументированное исключение с gap-x в AboutTraining.astro:

grep -rnE "\b(gap(-[xy])?|[mp][trblxy]?)-\[clamp" src/

Вертикаль секции задаётся только классом или токеном — своих формул в компонентах быть не должно (grep возвращает пусто):

grep -rnE "p[tby]?-\[clamp" src/components/ src/pages/

Фактический ритм на собранной странице (padding секций должен принимать ровно два значения — базовое и lg):

npm run build
npm run preview
# затем в DevTools:
# $$('section').map(s => [s.id, getComputedStyle(s).paddingTop])