/* Правки после аудита 2026-08-24: доступность и вёрстка.

   Отдельный лист, подключаемый последним, по той же причине, что и `outexp-cart.css`:
   `pages.css`, `home.css`, `catalog.css` и `doc.css` собраны прежним конвейером из `design/`
   в старом проекте, и ручная правка в них теряется при следующей сборке дизайна.
   Разбор находок — `docs/AUDIT-2026-08-24.md`, раздел A11. */

/* 1. Приглушённый текст не проходил AA ни на одном фоне.

   Замерено из вычисленных цветов: #7A868D давал 3.36 на основном фоне, 3.74 на белом и 3.12
   на подложке карточек при норме 4.5. Этим цветом набраны подписи конфигурации в корзине,
   плейсхолдеры полей, хлебные крошки, подпись согласия и вводные строки форм — то есть почти
   весь мелкий текст. Покупатель пергол в среднем старше сорока; это не формальность.

   #5A656B даёт 5.38 / 5.99 / 5.00 на тех же трёх фонах: даже на подложке карточки в
   корзине, самом тёмном из них, остаётся запас над порогом. Оттенок тот же, ниже яркость. */
:root {
  --ink-mute: #5A656B;
}

/* Плейсхолдер наследует цвет не везде — Chrome применяет свой полупрозрачный. */
.inp::placeholder,
.field::placeholder,
input::placeholder,
textarea::placeholder {
  color: var(--ink-mute);
  opacity: 1;
}

/* 2. Строка состояния приёма заявок читалась с контрастом 1.60 — почти невидимо.

   Это единственное место, где сайт честно говорит, что заявка не ушла и надо позвонить.
   Оно обязано читаться. Акцентный текст на акцентной подложке в 11 % — не читается. */
.mockbar {
  color: var(--ink);
  background: rgba(var(--accent-rgb), 0.14);
  border-left: 3px solid var(--accent);
}

/* 3. Заголовок для скринридера и поисковика там, где дизайн его не предполагает. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* 4. Зоны нажатия в корзине.

   Замерено на 390 px: кнопки количества 24×24, крестик удаления 19×26, чекбокс согласия
   13×13 при норме 44×44. Это ровно то, чем пользуются в корзине пальцем. Рамка не растёт —
   растёт область попадания, поэтому вид строки не меняется. */
.cqty .qbtn {
  position: relative;
  width: 32px;
  height: 32px;
}

.cqty .qbtn::after,
.crow .cdel::after,
.agree input::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
}

.crow .cdel {
  position: relative;
  width: 28px;
  height: 28px;
  font-size: 1.25rem;
}

.agree input {
  position: relative;
  width: 20px;
  height: 20px;
}

/* 5. Ошибки формы должны объявляться, а не только краснеть.

   Область живёт в разметке всегда — иначе скринридер не заметит её появления. */
[data-form-status] {
  margin: 0;
}

[data-form-status]:empty {
  display: none;
}


/* 6. Плитки моделей на главной.

   Раньше здесь стояли серые заглушки «Карточка 3×3» с `aspect-ratio: 1/1`, хотя кадры
   `card-*.webp` лежали в репозитории и работали в каталоге. Кадры подставлены, но высоту им
   задавала заглушка: без неё перголы вытянулись в узкие башни — проверено снимком.
   Пропорция кадра 4:3, показываем целиком на белом, как в каталоге. */
.peek-card {
  display: block;
  color: inherit;
}

/* `height: auto` обязателен: у картинки есть атрибуты width/height (они держат место до
   загрузки и убирают скачок вёрстки), и без сброса высоты браузер берёт 885 px из атрибута —
   плитка вытягивается в белую колонну, проверено замером. */
.peek-card img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: contain;
  background: var(--surface);
  border-radius: 4px;
}

.peek-card:hover .peek-meta {
  color: var(--accent-ink);
}

/* 7. Липкая шапка не должна накрывать то, к чему прокрутили.

   Замерено до правки: переход по `/#install`, `/#zayavka`, `/#catalog` прятал под шапкой
   60 px на десктопе и 69 px на телефоне — верхняя строка заголовка оказывалась за ней.
   Страдали не только якоря: Playwright отказывался нажимать переключатель цвета на карточке
   товара, сообщая, что нажатие перехватывает `.topin` — под шапку попадал любой орган
   управления, к которому прокрутили скриптом.

   `scroll-padding-top` задаётся прокручиваемому элементу (здесь это `html` — проверено
   через `document.scrollingElement` на всех страницах) и действует на ВСЕ виды прокрутки:
   переход по якорю, `scrollIntoView`, восстановление позиции. Поэтому одно правило вместо
   `scroll-margin-top` на каждой цели, которую кто-то не забудет пометить.

   96 px — самая высокая шапка (87 px на внутренних страницах) плюс воздух. На телефоне
   шапка ниже (69–73 px), и запас там больше; лишнее место над заголовком не мешает. */
html {
  scroll-padding-top: 96px;
}

/* 8. Замена формы заявки, когда скриптов нет. Живёт здесь, а не в бандле главной: бандлы
   пересобираются из `design/` в старом проекте, и ручная правка в них теряется. */
.leadnojs {
  padding: 26px 24px;
  border-radius: 14px;
  background: rgba(var(--accent-rgb), 0.12);
  border-left: 3px solid var(--accent);
}

.leadnojs h3 {
  margin: 0 0 8px;
  font-size: 1.15rem;
}

.leadnojs p {
  margin: 0 0 10px;
  font-family: 'Manrope', sans-serif;
  font-size: 0.9rem;
  line-height: 1.5;
}

.leadnojs-phone a {
  font-family: 'Manrope', sans-serif;
  font-size: 1.4rem;
  font-weight: 600;
  color: inherit;
  text-decoration: none;
  border-bottom: 2px solid var(--accent);
}

/* Замена мёртвых кнопок заказа на карточке товара, когда скриптов нет. */
.ctanojs {
  margin: 0 0 16px;
  padding: 14px 16px;
  border-left: 3px solid var(--accent);
  background: rgba(var(--accent-rgb), 0.12);
  font-family: 'Manrope', sans-serif;
  font-size: 0.9rem;
  line-height: 1.5;
}

.ctanojs a {
  font-weight: 600;
  color: inherit;
  white-space: nowrap;
}

/* 9. Зоны нажатия в подвале.

   Замерено на 360 px: ссылки 19 px в высоту при зазоре 13 px между соседями. Формально это
   проходит WCAG 2.2 AA (SC 2.5.8 разрешает цель мельче 24 px, если вокруг неё хватает
   свободного места), но не AAA (SC 2.5.5, 44 px) — а аудитория здесь покупатели загородных
   домов, возраст выше среднего, и попадание пальцем по строке в 19 px им стоит промахов.

   Доводить до полных 44 px значило бы прибавить подвалу больше 200 px на телефоне: ссылок
   девять, каждая выросла бы на 25 — и правка нужна только там, где тыкают пальцем.
   Поэтому она под `pointer: coarse`: на телефоне ссылка ровно 44 px при зазоре 8 (замерено
   после правки, наложения соседей нет, подвал вырос с 1092 до 1292 px), на десктопе подвал
   остаётся прежним — мышь в увеличенной цели не нуждается. */
@media (pointer: coarse) {
  .footgrid a:not(.logo):not(.fbtn),
  .legal a {
    display: inline-block;
    padding: 10px 0;
  }
}

/* 10. Поле-ловушка для роботов.

   Скрыто от всех, кроме программы, которая читает разметку: уводим за край окна и снимаем с
   потока. Не `display: none` — часть переборщиков такие поля пропускает; и не
   `visually-hidden`, который прячет только от глаз: скринридер прочитал бы поле вслух, и
   незрячий покупатель заполнил бы ловушку, оставшись без ответа на заявку. */
.trapfield {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* 11. Уровни заголовков приведены в порядок — вид при этом не меняется.

   Замер по сборке: `h1` на каждой странице ровно один (это было верно), но дальше уровни
   скакали — на карточке товара `h1 → h4`, на главной `h1 → h3`, в подвале везде `h2 → h4`.
   Человек, который ходит по странице заголовками (а слепой покупатель ходит именно так),
   слышит вложенность, которой нет: «В комплекте» звучало как подраздел несуществующего
   раздела.

   Стили в перенесённых бандлах привязаны к ТЕГУ (`.foot h4`, `.feats h3`, `.blk h4`), и
   смена уровня без этих правил изменила бы вид. Поэтому здесь повторены те же объявления
   для новых тегов — дословно, включая единицы измерения. Сверено попиксельно. */
/* `margin` и `line-height` здесь не для красоты: базовое правило бандлов перечисляет теги
   поимённо, и списки в разных бандлах РАЗНЫЕ — `product.css` пишет `h1,h2,h4`, а
   `catalog.css` только `h1,h4`. Новый `h2` в подвале каталога поэтому получал браузерный
   отступ по умолчанию, и страница выросла на 17 px. Поймано попиксельной сверкой: на
   карточке товара различий было ноль, на каталоге — нет. */
.foot h2 {
  margin: 0 0 16px;
  line-height: 1.1;
  font-size: 0.76rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--dark-mute);
  font-family: 'Manrope', sans-serif;
  font-weight: 600;
}

.feats h2 {
  /* `line-height` здесь НЕ задаём: `home.css` покрывает h2 базовым правилом и ставит 1.12.
     Навязанные 1.1 укоротили страницу на один пиксель — поймано попиксельной сверкой. */
  margin: 0 0 14px;
  font-size: 1.1rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.blk h2 {
  margin: 0 0 16px;
  font-family: 'Manrope', sans-serif;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-mute);
}

.cols h3 {
  margin: 0 0 16px;
  font-size: 1.05rem;
  font-weight: 600;
}

/* 12. Переход к содержимому.

   Спрятан не `display:none` и не нулевой прозрачностью — такие элементы фокус не получают
   вовсе, и ссылка была бы декорацией. Уводим за верхний край и возвращаем на месте, когда на
   неё встал фокус. `z-index` выше шапки (у неё 60), иначе она вернётся ПОД шапку и человек
   увидит, что фокус пропал. */
.skiplink {
  position: fixed;
  top: 0;
  left: 12px;
  z-index: 100;
  transform: translateY(-120%);
  padding: 12px 18px;
  border-radius: 0 0 10px 10px;
  background: var(--ink);
  color: #fff;
  font-family: 'Manrope', sans-serif;
  font-size: 0.9rem;
  font-weight: 600;
  text-decoration: none;
  transition: transform 0.16s ease;
}

.skiplink:focus {
  transform: translateY(0);
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Цель перехода принимает фокус, но не встаёт в порядок обхода: `tabindex="-1"`.

   Без него браузер по ссылке `#main` только прокручивает страницу, а фокус остаётся наверху —
   следующий Tab уводит человека обратно в меню, и переход не делает ровно того, ради чего
   существует. Проверено: до правки фокус после Enter оставался на `body`.

   Рамку у такой цели гасим: она обвела бы всё содержимое страницы. Видимый указатель фокуса
   при этом никуда не девается — он есть у всех настоящих органов управления. */
#main:focus {
  outline: none;
}

/* 13. Незаполненные места в юридических документах.

   Метку `[[УТОЧНИТЬ: …]]` в потоке текста глаз не замечает — владелец, просматривающий
   стенд, пролистает её вместе с абзацем. На боевой сайт такая метка не попадёт: сборка с
   ней не проходит `scripts/verify-website-build.mjs`. А на стенде она существует ровно для
   того, чтобы её увидели, — поэтому подсвечивается. */
mark.todo {
  display: inline;
  padding: 2px 6px;
  border-radius: 4px;
  background: #fde68a;
  color: #4a3b00;
  font-family: 'Manrope', sans-serif;
  font-size: 0.9em;
  font-weight: 600;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}

/* 14. Блок «Проекты» — настоящие фотографии с объектов.

   Прежняя вёрстка блока была лентой заглушек `.strip .ph` с горизонтальной прокруткой на
   пять «объектов». Настоящих кадров три, и на широком экране лента из трёх карточек шириной
   380 px выглядит как незаполненный ряд. Поэтому: на десктопе три равные колонки, на узком
   экране — прокрутка со снапом, где карточка занимает три четверти ширины и следующая
   выглядывает краем, показывая, что ряд продолжается.

   `aspect-ratio: 3 / 4` совпадает с пропорцией файлов (960×1280 после `build_works.py`), а
   `height: auto` обязателен: у картинок проставлены `width`/`height`, и без него браузер
   берёт высоту из атрибута — в этом проекте так уже вытянуло плитку каталога в белую
   колонну (см. A2 в аудите). Пропорция при этом задана в обоих местах намеренно: атрибуты
   держат место до загрузки, CSS — после. */
/* Кадров два, а не три (см. комментарий у блока в `index.astro`). Две колонки во всю ширину
   растянули бы каждую карточку в полстраницы: при пропорции 3:4 это почти 800 px высоты на
   кадр. Поэтому сетка сужена до ширины прежних двух карточек и поставлена по центру — размер
   кадра остаётся тем же, каким был при трёх, а пустого места справа не возникает. */
.worksgrid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(14px, 2vw, 26px);
  max-width: 880px;
  margin: 0 auto;
  padding: 0;
  list-style: none;
}

.workcard {
  margin: 0;
  overflow: hidden;
  border-radius: 14px;
  background: #e9ecee;
}

.workcard img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 4;
  object-fit: cover;
}

@media (max-width: 900px) {
  .worksgrid {
    display: flex;
    gap: 14px;
    margin-inline: calc(-1 * var(--gut, 20px));
    padding-inline: var(--gut, 20px);
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    /* Снап без этой строки съедает отступ от края экрана: он подтягивает начало карточки к
       границе контейнера, а не к границе его содержимого, и первый кадр упирается в край
       экрана, пока заголовок блока стоит с отступом. Поймано снимком, не глазами по коду. */
    scroll-padding-inline: var(--gut, 20px);
    -webkit-overflow-scrolling: touch;
  }

  .workcard {
    flex: 0 0 74%;
    scroll-snap-align: start;
  }
}

/* 15. Белая полоса между «Проектами» и блоком заявки.

   Класс `cta` в проекте занят дважды: на карточке товара это ряд кнопок, а на главной —
   тёмная секция с формой заявки. Правило, написанное для ряда кнопок, попадает и на секцию:
   `display: flex`, `align-items: center` и `margin-top: 40px`.

   Пока над секцией стоял блок цены без своего фона, зазор в 40 пикселей был не виден — он
   совпадал с фоном страницы. Стоило поставить выше блок «Проекты» с серым фоном, и зазор
   проявился светлой полосой во всю ширину. То есть ошибка была всё это время, а блок с
   фотографиями её только показал.

   Чиним точечно: секции возвращаем поведение секции. Ряд кнопок на карточке товара это
   правило не трогает — там селектор по-прежнему совпадает. */
section.cta {
  display: block;
  margin-top: 0;
}

/* 16. Список моделей в подвале: размер и площадь в двух колонках.

   Ширина первой колонки задана в `ch` по самой длинной строке — «3×3 усил.». Не в пикселях:
   шрифт подвала масштабируется вместе со страницей, и жёсткие пиксели разъехались бы на
   узком экране. */
.foot .fcat a {
  display: grid;
  grid-template-columns: 9.5ch auto;
  gap: 12px;
}

.foot .fcat .farea {
  color: var(--ink-mute);
}

/* 17. Три карточки-перехода под первым экраном.

   Кликается вся карточка, а не подчёркнутое слово: палец на телефоне попадает в неё не
   целясь. Внутри — заголовок, факт и «Подробнее ↓». Стрелка нужна не для красоты: она
   единственная говорит, что переход уводит вниз по этой же странице, а не на другую.

   Ссылка обёрнута вокруг заголовка, поэтому у неё убран цвет ссылки — иначе три синих
   заголовка читались бы как ошибка вёрстки. Подчёркивание появляется при наведении и на
   фокусе с клавиатуры. */
.jcard {
  display: block;
  color: inherit;
  text-decoration: none;
  padding: clamp(20px, 2.4vw, 30px);
  border-radius: 16px;
  background: var(--bg, #fff);
  transition: transform .18s ease, box-shadow .18s ease;
}

.jcard h2 {
  margin: 0 0 10px;
}

.jcard p {
  margin: 0 0 16px;
}

.jmore {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: 'Manrope', sans-serif;
  font-size: 0.92rem;
  font-weight: 600;
  border-bottom: 1px solid currentColor;
  padding-bottom: 2px;
}

.jcard:hover,
.jcard:focus-visible {
  transform: translateY(-2px);
  box-shadow: 0 10px 30px rgba(20, 30, 35, .10);
}

/* Движение — только тем, кто его не просил отключить. */
@media (prefers-reduced-motion: reduce) {
  .jcard {
    transition: none;
  }

  .jcard:hover,
  .jcard:focus-visible {
    transform: none;
  }
}

/* Цель перехода принимает фокус, но рамкой не обводится: она обвела бы весь экран.
   Без `tabindex="-1"` на секции фокус остаётся в шапке, и следующий Tab уводит человека
   обратно в меню — переход не делает того, ради чего существует. */
#dostavka:focus,
#sborka:focus,
#lameli:focus {
  outline: none;
}

/* 18. Ряд фотографий в блоке про ламели — та же механика, что у «Проектов»:
   три колонки на широком экране, прокрутка со снапом на узком. Кадры 4:3, пропорция задана
   и в файле, и в CSS, поэтому лента не дёргается при загрузке. */
.photorow {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(14px, 2vw, 26px);
  margin: 0;
  padding: 0;
  list-style: none;
}

.photorow figure {
  margin: 0;
}

.photorow img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: 14px;
  background: #e9ecee;
}

.photorow figcaption {
  margin-top: 10px;
  font-size: 0.92rem;
  opacity: .8;
}

@media (max-width: 900px) {
  .photorow {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: var(--gut, 20px);
    margin-inline: calc(-1 * var(--gut, 20px));
    padding-inline: var(--gut, 20px);
    gap: 14px;
  }

  .photorow > li {
    flex: 0 0 74%;
    scroll-snap-align: start;
  }
}

/* 19. Два способа управления ламелями — рядом, чтобы разница читалась без чтения. */
.ctrl {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(20px, 3vw, 44px);
  margin-top: clamp(28px, 4vw, 52px);
}

.ctrl h3 {
  font-size: clamp(1.1rem, 1.6vw, 1.35rem);
  margin: 0 0 8px;
}

.ctrl p {
  margin: 0;
}

@media (max-width: 760px) {
  .ctrl {
    grid-template-columns: 1fr;
  }
}

/* 20. Кадр комплекта в блоке «Установка»: во всю ширину колонки, подпись под ним. */
.kitshot {
  margin: 0;
}

/* Кадр комплекта: небольшой квадрат по центру, под ним подпись и кнопка. */
.kitcol {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

.kitshot {
  width: min(100%, 460px);
}

.kitcap {
  max-width: 46ch;
  margin: clamp(18px, 2.4vw, 26px) 0 clamp(20px, 2.6vw, 30px);
  font-size: clamp(1rem, 1.3vw, 1.15rem);
}

.kitshot img {
  display: block;
  width: 100%;
  height: auto;
  /* Пропорция совпадает с файлом (960 × 960), поэтому место под кадр занято до его
     загрузки и вёрстка не дёргается. */
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: 16px;
  background: #e9ecee;
}



/* 21. Список инструкций: строка на модель, ссылка на файл рядом. */
.instr {
  padding-left: 0;
  list-style: none;
}

.instr > li {
  padding: 12px 0;
  border-bottom: 1px solid rgba(20, 30, 35, .12);
}

.instr-none {
  color: var(--ink-mute);
}

/* 22. Приглушённый текст на тёмной секции.

   `--ink-mute` (#5A656B) подобран под светлый фон и даёт на нём 5.38. На тёмной подложке
   #222A2E тот же цвет даёт 2.44 при норме 4.5 — я это и получил, поставив `.sub` в блок про
   ламели. Тёмным секциям нужен свой приглушённый тон: #B7C2C7 даёт на той же подложке 8.6,
   оставаясь тише белого заголовка. */
.section-dark .sub,
.section-dark .pricenote,
.section-dark .dostnote {
  color: #B7C2C7;
}

/* 23. Ритм в середине страницы: «Установка» → «Доставка» → «Цена».

   Три секции подряд построены одинаково — заголовок, линия, три числа. У каждой свои 144 px
   отступа, и на одинаковом фоне они сначала дают провал в 288 px между двумя строчками, а
   если провал убрать — сливаются в одно длинное полотно. Владелец увидел оба состояния.

   Лечим двумя вещами сразу: фон у «Цены» теперь серый (граница видна сама собой, `.works`
   ниже отдал серый, чтобы секции продолжали чередоваться), а отступы на этих стыках срезаны
   примерно вдвое — 144 px были рассчитаны на редкие крупные секции, а не на три подряд. */
#sborka {
  padding-bottom: clamp(48px, 6vw, 88px);
}

#dostavka {
  padding-top: clamp(48px, 6vw, 88px);
  padding-bottom: clamp(48px, 6vw, 88px);
}

#dostavka + .price-block {
  padding-top: clamp(48px, 6vw, 88px);
}

/* 24. Блок «Масштаб» и полоса-разделитель — визуализации.

   Плашка «Визуализация» лежит НА кадре, а не подписью под ним и не сноской в подвале: подпись
   отрывается от изображения при пересылке скриншота, а сноска мелким шрифтом — это сокрытие
   существенной информации (ч. 7 ст. 5 ФЗ «О рекламе»). Подложка почти непрозрачная (0.82) и
   текст белый не для красоты: у `vis-3x6` верх кадра — сплошное светлое небо, и плашка со
   слабой подложкой легла бы светло-серым по белому.

   `aspect-ratio` продублирована атрибутами `width`/`height` в разметке: атрибуты держат место
   до загрузки, CSS — после. Без `height: auto` браузер возьмёт высоту из атрибута и вытянет
   кадр в колонну — эти грабли в проекте уже были на плитке каталога (A2 в аудите).

   Полоса не требует приёмов вроде `width: 100vw`: она лежит на верхнем уровне страницы,
   сиблингом секций, и уже занимает всю ширину. `100vw` здесь дал бы горизонтальную прокрутку
   на ширину полосы прокрутки — известная ловушка, и в этом проекте `overflow-x: hidden` уже
   один раз ломал липкую шапку, второй раз наступать не будем. */
.visgrid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(16px, 2.4vw, 30px);
  max-width: 960px;
  margin: 0 auto;
  padding: 0;
  list-style: none;
}

.viscard {
  margin: 0;
}

.vislink {
  display: block;
  color: inherit;
  text-decoration: none;
}

.visshot {
  display: block;
  position: relative;
  overflow: hidden;
  border-radius: 14px;
  background: #e9ecee;
}

.visshot img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}

.visbadge {
  position: absolute;
  top: 12px;
  left: 12px;
  padding: 5px 10px;
  border-radius: 999px;
  background: rgba(20, 24, 26, 0.82);
  color: #fff;
  font-family: 'Manrope', sans-serif;
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.visarea {
  display: block;
  margin-top: 16px;
  font-family: 'Manrope', sans-serif;
  font-size: 1.15rem;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.vistext {
  display: block;
  margin-top: 6px;
  color: var(--ink-mute);
  font-size: 0.95rem;
}

.visprice {
  display: block;
  margin-top: 10px;
  font-family: 'Manrope', sans-serif;
  font-weight: 600;
}

.vislink:hover .visarea,
.vislink:focus-visible .visarea {
  text-decoration: underline;
}

.visband {
  position: relative;
  display: block;
  line-height: 0;
}

.visband img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 21 / 9;
  object-fit: cover;
}

.visbadge-band {
  top: auto;
  bottom: 12px;
  left: 16px;
  line-height: 1;
}

@media (max-width: 700px) {
  .visgrid {
    grid-template-columns: minmax(0, 1fr);
    gap: 26px;
  }
}

/* 25. Шов между каталогом и «Масштабом», и плашка на широкой полосе.

   Обе секции — обычные `.section` на одном фоне, и между ними складывались два вертикальных
   отступа подряд: `--pad` это clamp(76px,10vw,150px), то есть на широком экране до 300 px
   пустоты, где каталог кончился, а следующий блок ещё не начался. Границы там не видно вовсе —
   читается как обрыв страницы, а не как переход. Раньше этого не было, потому что за каталогом
   сразу шла `section-alt`: смену блока держал фон, а не расстояние.

   Красить `.vis` в `--bg-alt` нельзя — следом идёт `.sborka`, она уже `section-alt`, и две
   секции слились бы теперь снизу. Поэтому шов держится двумя вещами: отступы с обеих сторон
   ополовинены и между ними проведена линия по ширине контента.

   Линия — псевдоэлемент на секции, а не `border-top` на первом `.wrap`: у блока заголовка
   стоит `max-width: 660px`, и рамка на нём вышла бы обрубком в половину экрана.

   Плашка полосы переехала из левого нижнего угла в левый верхний. Внизу она ложилась на
   тёмный низ кадра — ствол оливы и тень, — и тёмная подложка с белым текстом там пропадала;
   вверху под ней сплошное светлое небо. Подложка заодно уплотнена до 0.9 и получила тень:
   кадрирование `object-fit: cover` на узком экране сдвигает картинку, и упереться в то, что
   в левом верхнем углу всегда небо, нельзя. */
/* ШОВ СНЯТ 2026-09-01 вместе с самой секцией каталога: владелец убрал её с главной целиком.
   Выше `.vis` теперь стоит тёмная `lameli`, и границу держит смена фона — ровно так, как было
   до появления каталога на главной. Рассуждение выше оставлено: если секцию вернут, вернётся
   и склейка двух светлых блоков, и лечить её придётся тем же способом. */

.visbadge-band {
  top: 14px;
  bottom: auto;
  left: 16px;
  background: rgba(20, 24, 26, 0.9);
  box-shadow: 0 1px 6px rgba(0, 0, 0, 0.25);
}

/* Уменьшенная плашка на телефоне здесь была, пока полоса резалась 32:9 — при ширине 390 px
   это давало 110 px высоты, и пометка прежнего размера занимала пятую часть полосы. После
   возврата к 21:9 полоса на телефоне 167 px, и обычная плашка в неё садится нормально.
   Правило снято, а не оставлено «на всякий случай»: мёртвое правило дороже отсутствующего —
   следующий человек будет гадать, от чего оно лечит. */

/* 26. Кнопка «Оставить заявку» в подвале.

   Она была кнопкой ровно на одной странице сайта. Класс `.fbtn` описан только в
   `catalog.css`, а подвал общий: главная грузит `home.css`, карточка товара — `product.css`,
   документы — `pages.css` и `doc.css`. На всех этих страницах последний призыв к действию
   выглядел обычной строкой текста среди других ссылок подвала — и терялся между «Все
   контакты» и списком моделей. Поэтому правило переехало сюда: этот лист подключается
   последним на каждой странице.

   Заливка акцентом, а не рамка: подвал тёмный, и контур из `--dark-line` (белый на 16 %)
   от фона почти не отличается — это и была прежняя кнопка каталога.

   Светлая рамка поверх заливки нужна не для красоты. Сам акцент `#3E6B7B` на фоне подвала
   `#222A2E` даёт всего 2.5:1 — под порогом 3:1 для границ органов управления (WCAG 1.4.11),
   то есть форму кнопки глазом видно плохо. Рамка `rgba(241,243,244,0.45)` поверх акцента
   даёт границу с контрастом 5.85:1. Текст `--accent-on` на акценте — 5.15:1. Оба посчитаны,
   а не прикинуты.

   `align-self: start` обязателен: колонка подвала — grid, и без него кнопка растянулась бы
   на всю ширину колонки, а рядом стоят короткие ссылки. */
.fbtn {
  display: inline-flex;
  align-self: start;
  align-items: center;
  justify-content: center;
  margin-top: 14px;
  padding: 14px 28px;
  font-family: 'Manrope', sans-serif;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--accent-on);
  background: var(--accent);
  border: 1px solid rgba(241, 243, 244, 0.45);
  border-radius: 3px;
  text-decoration: none;
  transition: background-color .2s, border-color .2s;
}

.fbtn:hover {
  background: var(--accent-hover);
  border-color: rgba(241, 243, 244, 0.75);
}

.fbtn:focus-visible {
  outline: 2px solid var(--dark-ink);
  outline-offset: 3px;
}

/* 27. Иллюстрации на странице инструкций.

   Страница была сплошным текстом: пять заголовков, три списка и абзацы предупреждений подряд.
   Человек, который открыл её перед сборкой, ищет глазами не абзац, а картинку — «чем крутить»
   и «в каком виде приедет». Поэтому иллюстраций ровно две и обе отвечают на вопрос, а не
   украшают: комплект на брусчатке и сетка инструмента из заводской инструкции.

   Обтекание, а не полоса во всю ширину: рисунок в 360–420 px не разрывает чтение, и текст
   идёт рядом. На узком экране обтекание снимается — колонка там и так одна, а картинка,
   зажатая в треть ширины, перестаёт читаться.

   `shape-margin` не ставим: обе картинки прямоугольные, `margin` достаточно. */
.doc-fig {
  float: right;
  width: min(300px, 34%);
  margin: 4px 0 20px 26px;
}

/* Сетка инструмента уже, чем кадр комплекта, и это не про вкус. Кадр комплекта квадратный:
   при 300 px он и 300 px высотой, что как раз под вводные абзацы. Сетка почти квадратная и
   стоит рядом со списком из четырёх пунктов — если дать ей ту же ширину, список обтекает её
   в две трети колонки и строки рвутся на три слова. */
.doc-fig-tools {
  width: min(268px, 32%);
}

/* Заголовок раздела обязан начинаться под своей иллюстрацией, а не обтекать чужую.
   Без этого при коротком тексте картинка одного раздела сползает в следующий: проверено на
   живой странице — сетка инструмента уехала к «Что важно знать до установки», за экран от
   списка, который она поясняет. */
.doc h2 {
  clear: both;
}

.doc-fig img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 10px;
}

/* Сетка инструмента — белый лист документа. На светлом фоне страницы у неё нет края, и она
   висит в воздухе обрывком; тонкая рамка возвращает ей вид фрагмента бумаги. Фотографии
   комплекта рамка не нужна — у неё край держит сам кадр. */
.doc-fig-tools img {
  border: 1px solid var(--line);
  background: #fff;
}

.doc-fig figcaption {
  margin-top: 10px;
  font-family: 'Manrope', sans-serif;
  font-size: 0.82rem;
  line-height: 1.45;
  color: var(--ink-mute);
}

/* Список инструкций: кадр модели слева, текст справа. `align-items: start` — иначе строка
   без файла (у усиленной 4×3 инструкции пока нет, и там длинная строка с телефоном)
   растянула бы кадр по высоте всей строки. */
.instr-thumbs li {
  display: flex;
  align-items: start;
  gap: 16px;
}

.instr-thumb {
  flex: 0 0 auto;
  width: 84px;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: contain;
  background: var(--surface, #fff);
  border-radius: 6px;
}

.instr-body {
  min-width: 0;
}

@media (max-width: 700px) {
  .doc-fig,
  .doc-fig-tools {
    float: none;
    width: 100%;
    margin: 18px 0 24px;
  }

  .instr-thumb {
    width: 62px;
  }
}

/* 28. Высота героя и кадрирование кадра по вертикали. Правится ТОЛЬКО вместе.

   Задача была показать одновременно верхнюю балку перголы и опорные пластины под стойками,
   а над балкой не оставить неба: владелец попросил прямо — «чтобы неба над перголой не было,
   но текст оставался в перголе».

   Конструкция занимает в ИСХОДНОМ кадре 0.585 высоты: верхняя балка на 0.201, передние
   пластины на 0.785 (замерено по разметке, не на глаз; задние пластины выше — на 0.72, и по
   ним легко обмануться). Герой показывал 0.591 высоты кадра — ровно столько же. Полей не
   оставалось вовсе, и любой сдвиг ради одного края срезал другой: 25 % — есть кровля, нет
   пола; 35 % — есть небо, нет передних ножек; 65 % — есть ножки, срезана балка.

   ПЕРВОЕ РЕШЕНИЕ (2026-09-01, утро) было долевым: высота `max(88vh, 48.5vw)` плюс
   `object-position: center 53.8%`. Оно работало ровно на одном размере окна и разъезжалось
   на всех остальных, потому что доля видимой части кадра зависит от высоты героя: на
   1539×845 над балкой оставалось 13 px неба, на 1920×1080 — 11, а на 1440×900 уже 53.
   Одним процентом это не чинится: чем у́же окно, тем бо́льшую долю кадра видно и тем ниже
   опускается балка.

   ВТОРОЕ РЕШЕНИЕ (2026-09-01, вечер) переносит срез из стилей в конвейер. Небо срезано у
   самого исходника — `tools/visuals/build_visuals.py`, параметр 0.20201, 724 строки из 3584,
   кадр стал 4800×2860 с пропорцией 0.5958. Верх кадра теперь И ЕСТЬ верхняя кромка балки,
   поэтому здесь достаточно прижать кадр к верху:

       object-position: center top;

   Это работает на любой ширине окна без медиазапросов и подобранных процентов. Более того,
   оно не ломается там, где кадр по высоте совпадает с блоком (телефон, узкие окна): при
   `object-fit: cover` вертикального запаса там нет, и вертикальная позиция просто ни на что
   не влияет — обрезается ширина. Долевые смещения в этом случае оставляли бы щель.

   Высота `clamp(560px, max(88vh, 48.5vw), 1000px)` оставлена прежней, но её смысл сменился.
   Теперь 48.5vw держит НИЖНЮЮ границу: она даёт 0.814 нового кадра, то есть окно героя
   доходит до 0.851 исходника при пластинах на 0.785 — под ногами перголы остаётся полоса
   брусчатки. Нижний предел этой величины 43.5vw: на нём пластины упираются в край экрана.
   Прежний `88vh` побеждает на высоких окнах, потолок 1000px не даёт герою разрастись на
   очень широких мониторах.

   Совмещение шапки с балкой раньше задавалось теми же 53.8 % и было точным только на том
   размере, где мерялось. Теперь балка прижата к верху и занимает 0.0604 нового кадра, то
   есть 3.6 % ширины окна: 55 px на 1539, 52 на 1440, 46 на 1280. Центр меню всегда на 43 —
   значит меню лежит на балке от 1200 px и шире. Ниже 1200 балка становится у́же меню; там
   выручает бургер (см. блок 29), но само совмещение перестаёт быть точным.

   Что проверять при замене кадра героя: верхняя кромка балки должна совпасть с верхом
   исходника (иначе вернётся небо или срежется балка), а передние опорные пластины — попасть
   в 0.814 высоты нового кадра. */
.hero {
  min-height: clamp(560px, max(88vh, 48.5vw), 1000px);
}

.hero-bg img {
  object-position: center top;
}

/* 29. Шапка героя выровнена по краям верхней балки перголы.

   Владелец нарисовал две вертикальные красные линии по концам балки и стрелки наружу:
   логотип слева и «Корзина» справа должны стоять ровно на них. Раньше правило решало
   более скромную задачу — «не вылезать за балку» — и держало заметный зазор.

   Линии стоят на ШВЕ, а не на внешнем силуэте балки, и разница здесь принципиальная. У балки
   есть торцевая закрышка: внешний край её тени — 0.1305 и 0.8721 ширины кадра, а шов, где
   закрышка переходит в лицевую плоскость, — **0.1492 и 0.8532**. Между ними 35 px на окне
   1830. Человек читает краем перголы именно шов, и владелец провёл линии по нему: на его
   скриншоте шириной 1830 они пришлись на 273 и 1561, а расчёт по долям даёт 273.0 и 1561.4.

   Шов найден не на глаз: в профиле яркости кадра это провал 56 против 77 в столбце 283 слева
   и 55 против 63 в столбце 1621 справа. По снимку страницы такое мерить нельзя — там край
   размывается сглаживанием и градиентом героя.

   Итого ширина 0.8532 − 0.1492 = **0.7040**, центр 0.5012 — на 0.12 % правее середины кадра.
   Мелочь, но `margin: 0 auto` уводило бы шапку на 2 px влево от линии, поэтому положение
   задаётся левым краем: `margin-left: 14.92%` плюс ширина `70.4%`.

   Единицы — ПРОЦЕНТЫ, а не `vw`, и это не вкусовщина. `100vw` включает полосу прокрутки, а
   ширина `.nav` и кадра — нет; на настоящем Chrome под Windows с классической полосой это
   разошлось бы на те же 2 px, которые мы только что убрали. Проценты считаются от `.nav`,
   а он растянут `left:0;right:0` по той же коробке, что и кадр.

   Кадр в герое масштабируется по ширине (`object-fit: cover` при блоке шире кадра), поэтому
   доля постоянна и попадание в край балки не зависит от ширины окна. Проверено рендером на
   1920, 1830, 1539, 1440, 1280 и 900.

   Зануление полей обязательно. `.navin` — это `.wrap`, у него `padding: 0 clamp(20px,5vw,84px)`,
   и без обнуления `max-width` задаёт внешнюю коробку, а содержимое остаётся у́же на два
   отступа: на 1539 это было 923 px против 1141 px балки, по 109 px пустоты с каждой стороны.
   Именно её владелец и увидел.

   Ограничение `min(1280px, …)` снято намеренно. Оно оставляло широким экранам «прежнюю
   ширину», но теперь шапка обязана следовать за перголой, а та на 1920 — это 1352 px. На
   очень широких мониторах связка не ломается: герой упирается в потолок высоты 1000px, но
   кадр всё равно масштабируется по ширине (0.5958 × 2560 = 1525 > 1000), значит доля 0.7040
   держится и там.

   Где выравнивание становится приблизительным: на высоких окнах и пропорции 4:3
   (H > 0.5958W — например 1024×768 или 1280×1024) кадр масштабируется уже по ВЫСОТЕ и
   обрезается по бокам, отчего пергола на экране становится ШИРЕ 70.4 %. Шапка тогда стоит с
   запасом внутри — ошибка в безопасную сторону, за перголу она не вылезет никогда.

   Правило только для широких экранов: ниже 900 px меню сворачивается в бургер, и его ширина
   к балке отношения не имеет. */
@media (min-width: 900px) {
  .hero .nav .navin {
    max-width: 70.4%;
    margin-left: 14.92%;
    margin-right: auto;
    padding-left: 0;
    padding-right: 0;
  }
}

/* 30. Полоса 861…900 px, где на лендинге не было НИ меню, НИ бургера.

   Найдено замером при выравнивании шапки по перголе (блок 29): на 900 px `.links` уже
   `display: none`, а `.burger` ещё `display: none` — навигации на первом экране нет вообще.

   Причина — два разных порога в `home.css`: ссылки прячет `@media(max-width:900px)`
   (строка 544), а бургер показывает `@media(max-width:860px)` (строка 248). Сорок пикселей
   между ними никем не покрыты. На остальных страницах такого нет: там и `.topnav`, и бургер
   переключаются на 860.

   Здесь порог бургера поднят до 900 — то есть до того же числа, на котором исчезают ссылки.
   Правило намеренно узкое (`.hero .burger`): общий порог трогать нельзя, он общий для пяти
   бандлов старого сайта, и его сдвиг поехал бы по всем страницам сразу. */
@media (max-width: 900px) {
  .hero .burger {
    display: flex;
  }
}

/* 31. Шапка героя стоит ПОСЕРЕДИНЕ балки по высоте.

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

   Вертикаль до этого не трогали вовсе. Отступ `.nav { padding: 26px 0 }` достался от старого
   кадра С НЕБОМ: там балка проходила по середине шапки сама собой, а после среза неба (блок
   28) балка уехала к верху экрана, отступ же остался прежним. Центр содержимого шапки — 43 px
   при середине балки 33 px.

   Центрировать надо по ЛИЦЕВОЙ ГРАНИ балки, а не по всей тёмной полосе — и это стоило
   отдельного захода. Сначала я взял границу там, где кончается темнота: строки 0…69.5 из
   1132, доля 0.0614. Владелец посмотрел и сказал «ещё чуть выше», и он был прав. Профиль
   яркости по чистым столбцам показывает ступень на строке 61: до неё ровные 63, после —
   75. Это нижняя кромка балки, поймавшая свет; ламели начинаются ещё ниже, со строки 70,
   где яркость снова прыгает до 83 и 91.

   Глаз считает «палкой» ровную грань и не засчитывает подсвеченную кромку. Разница всего
   восемь строк кадра, но на экране это 4 px смещения текста вниз — ровно столько, сколько
   владелец увидел. Поэтому доля **0.05345** (60.5 из 1132), а не 0.0614.

   Мораль на будущее: «где кончается тёмное» и «где кончается грань» — разные вещи, и вторую
   даёт только профиль яркости, а не порог по темноте.

   Высота грани на экране зависит от того, чем масштабируется кадр:

     • кадр по ширине (обычные окна 16:9 и 16:10) — 0.05345 × 0.5958 × ширина = 3.1846vw;
     • кадр по высоте (окна 4:3 и высокие, где H > 0.5958W) — 0.05345 × высоты героя.

   Отсюда `max()` из двух выражений: второе — это высота героя из блока 28, умноженная на
   0.05345 почленно (умножение `clamp()` на положительное число законно). Так центрирование
   остаётся точным на ЛЮБОЙ форме окна, а не только на том, где мерялось. Проверено на
   1825×890, 1920×1080, 1440×900, 1280×800 и 1024×768.

   Центрируется флексом, а не подобранным `padding-top`: высота содержимого шапки зависит от
   логотипа (две строки, «OUT EXP» плюс подпись), и любое число пришлось бы пересчитывать
   после правки шрифта. `align-items: center` пересчитает сам.

   `.drawer` лежит в том же `.nav`, но флекс-элементом не становится: ниже 900 px это правило
   не действует, а выше — у шторки `display: none`. */
@media (min-width: 900px) {
  .hero .nav {
    height: max(3.1846vw, clamp(29.932px, max(4.7036vh, 2.5923vw), 53.45px));
    padding: 0;
    display: flex;
    align-items: center;
  }

  .hero .nav .navin {
    width: 100%;
  }
}

/* 32. Цифры Raleway — ЛИНЕЙНЫЕ, а не старостильные.

   Владелец: «на карточках товара размер не посерединке блока, и цифры какие-то неровные…
   все ровные как 3×3». Он прав дважды, и причина у обеих жалоб одна.

   У Raleway по умолчанию СТАРОСТИЛЬНЫЕ цифры (old-style figures): часть знаков свисает ниже
   базовой линии, часть поднимается выше высоты строчных. Замерено по чернилам на снимке
   карточки (390 px, DPR 3):

       «3» — ink 88…131, свисает под базовую линию
       «6» — ink 79…122, поднимается над строчными
       «4» — ink 88…130, свисает

   Отсюда «3×3» выглядит ровно (обе тройки свисают одинаково), а «3×6» — нет: тройка внизу,
   шестёрка вверху. И отсюда же «не посерединке»: у «3×3» чернила уходили на 2.5 px ниже
   середины полосы, хотя КОРОБКА текста была отцентрована идеально — поэтому по разметке
   дефект не виден вовсе, только глазом или по пикселям.

   `font-variant-numeric: lining-nums` включает в том же шрифте линейный набор (`lnum`).
   Проверено рендером: обе цифры становятся 79…122, смещение чернил от середины полосы
   падает с +2.5 px до −0.5 px. Менять сам шрифт не понадобилось.

   `tabular-nums` добавлен только размерам на карточках и переключателе моделей: там цифры
   стоят столбиком от карточки к карточке, и одинаковая ширина знака держит их в линию.
   Заголовкам табличные цифры не нужны — в тексте они выглядят разреженно.

   Правило распространено на ВСЕ места, где Raleway показывает числа, а не только на карточки
   каталога: тем же набором набраны заголовки товара («Навес-пергола 3×4 Графит»), крупные
   числа `.bignum` и `.facts .big` («80–120 кг/м²»). Дефект там тот же самый, и чинить его
   отдельным заходом смысла нет. */
h1,
h2,
h3,
h4,
.trow .lbl,
.bignum,
.facts .big {
  font-variant-numeric: lining-nums;
}

.cname,
.mname {
  font-variant-numeric: lining-nums tabular-nums;
}

/* 33. `<picture>` вокруг кадра героя не должен ломать его растяжку.

   У телефона с 2026-09-01 свой кадр, и ради `<source media>` картинка обёрнута в `<picture>`.
   Обёртка не бесплатна: `picture` — строчный элемент, высота у него по содержимому, а у
   картинки внутри стоит `height: 100%`. Процент считается от высоты РОДИТЕЛЯ, то есть от
   самой обёртки, у которой высота ещё не определена, — и `height` схлопывается в `auto`.
   Фон героя при этом не тянется на всю секцию, а ложится полосой по своей высоте.

   Поэтому обёртка сама становится блоком во весь `.hero-bg`. Правило нужно ровно одно, но
   без него ломается первый экран целиком. */
.hero-bg picture {
  display: block;
  width: 100%;
  height: 100%;
}

/* 34. СНЯТО в тот же час, когда поставлено. Здесь была мягкая подложка под текстом первого
   экрана на телефоне: замер показал контраст 3.30 при норме 4.5, и радиальное пятно
   alpha 0.30 поднимало его до 4.69, не затемняя кадр целиком.

   Цифры были верные, а решение — нет. Владелец открыл страницу и увидел «тёмную рамку вокруг
   текста». Причина в том, чего замер не показывает: **новая стена ровная и однотонная**, и на
   ней любой мягкий градиент читается как пятно с краями. Прятать его не за что — ни фактуры,
   ни перепада яркости. На пёстром фоне тот же слой был бы незаметен.

   Правильное место для этой правки — не стили, а сам кадр: стена в текстовой полосе должна
   быть темнее. Светлой она вышла по моему же заданию генератору («evenly lit, no strong
   shadow patterns»), так что чинить надо там.

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

/* 35. СНЯТО вместе с кадром, ради которого ставилось. Здесь верх вуали на телефоне был
   поднят с 0.46 до 0.58: в том кадре шапка приходилась не на сплошную балку, а на просветы
   между ламелями, сквозь них било небо, и белый логотип давал 2.70 при норме 3.0.

   Кадр заменён — у прежнего была неверная конструкция кровли (ламели под разными углами и
   без бокового рельса), владелец это увидел. В нынешнем шапка снова лежит на сплошной балке,
   и с обычной вуалью 0.46 контраст 8.95. Затемнять первый экран было бы нечем оправдать.

   Правило: перед тем как усиливать вуаль ради контраста, проверить, не изменился ли кадр под
   ней. Затемнение — плата за кадр, а не за вёрстку. */
