/* Надстройка над viewer/styles.css.
   Основные стили не трогаем: на них живёт прежняя витрина, и расхождение
   двух копий обошлось бы дороже, чем этот файл. Здесь только то, что появилось
   от перехода на серверный рендер: бывшие кнопки стали ссылками, добавились
   хлебные крошки и вводный абзац категории. */

/* Кликабельное теперь ссылка, а не кнопка: браузер по умолчанию подчёркивает
   и красит их, а макет рассчитан на нейтральный текст. */
a.card__link,
a.cat,
a.brand-chip,
a.chip,
a.variant,
a.pdp__tag,
a.pl-link,
a.pl-thumb,
a.info-card,
a.cat-tile {
  text-decoration: none;
  color: inherit;
}

a.card__link:hover { text-decoration: none; }
a.chip:hover { color: var(--accent); }

/* ---------- хлебные крошки ---------- */

.crumbs {
  padding: 2px 0 14px;
  font-size: 13px;
  color: var(--text-faint);
}
.crumbs ol {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  margin: 0; padding: 0; list-style: none;
}
.crumbs li { display: flex; align-items: center; gap: 6px; min-width: 0; }
.crumbs li + li::before { content: "/"; color: var(--border-strong); }
.crumbs a { color: inherit; text-decoration: none; }
.crumbs a:hover { color: var(--accent); }
.crumbs span[aria-current] {
  color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 46ch;
}

/* ---------- плитки категорий ---------- */

/* Фон у фотографий белый и запечён в сам файл, поэтому цветная подложка
   внутри карточки давала «фон — рамка — фон». Белый блок фото тянется от
   края до края, как у товарных карточек, подписи — ниже на поверхности. */
.cat-tile { padding: 0; overflow: hidden; }
.cat-tile__media {
  margin-bottom: 6px;
  border-radius: 0;
  background: #fff;
}
.cat-tile__media img { padding: 16px; }
.cat-tile__name { padding: 0 14px; }
.cat-tile__count { padding: 0 14px 12px; }

/* ---------- строка подразделов ---------- */

/* Подразделы и «Другие разделы» на странице раздела — не сетка плиток, а
   строка круглых ссылок: кружок с фото, название, счётчик. Сеткой они стояли
   над товарами в той же рамке и с тем же фото и читались как первый ряд
   карточек — на аксессуарах и одежде люди пытались «купить» подраздел.
   Классы плиток те же, что на главной (.cat-tile): на них держатся скелет и
   проявление картинки, — вид переопределяет обёртка. Фон — поверхность без
   рамки, чтобы не спорить ни с карточками, ни с чипами брендов. */
.cat-strip { margin-bottom: 22px; }
.cat-strip--siblings { margin: 44px 0 0; }
.cat-strip__title { margin-bottom: 10px; }
.cat-strip__list { display: flex; flex-wrap: wrap; gap: 8px; }
.cat-strip .cat-tile {
  flex-direction: row; align-items: center; gap: 10px;
  padding: 5px 16px 5px 5px;
  background: var(--surface-2); border: 0; border-radius: 100px;
  transition: background .14s;
}
.cat-strip .cat-tile:hover { transform: none; box-shadow: none; background: var(--border); }
.cat-strip .cat-tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.cat-strip .cat-tile__media {
  width: 44px; height: 44px; aspect-ratio: auto; flex: none;
  margin: 0; border-radius: 50%; background: #fff;
  animation: none;
}
.cat-strip .cat-tile__media img { padding: 5px; }
.cat-strip .cat-tile__media .icon--ghost { width: 18px; height: 18px; }
.cat-strip .cat-tile__text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.cat-strip .cat-tile__name { padding: 0; font-size: 13.5px; line-height: 1.2; white-space: nowrap; }
.cat-strip .cat-tile__count { padding: 0; font-size: 11.5px; line-height: 1.2; white-space: nowrap; }

/* На телефоне — одной прокручиваемой строкой до края экрана, как чипы
   фильтров в мобильных магазинах: три ряда кружков съедали бы первый экран.
   Отрицательные поля выводят строку из отступа .shell, чтобы чипы уезжали за
   край, а не обрывались на нём. */
@media (max-width: 620px) {
  .cat-strip { margin-bottom: 16px; }
  .cat-strip--siblings { margin-top: 32px; }
  .cat-strip__list {
    flex-wrap: nowrap; gap: 6px;
    overflow-x: auto; overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
    margin: 0 calc(-1 * var(--gutter)); padding: 0 var(--gutter) 4px;
    /* Без этого первая плитка прилипала к краю экрана: точка привязки
       отсчитывается от края прокрутки, а не от внутреннего отступа. */
    scroll-padding-inline: var(--gutter);
    scrollbar-width: none;
  }
  .cat-strip__list::-webkit-scrollbar { display: none; }
  .cat-strip .cat-tile { flex: none; scroll-snap-align: start; padding: 4px 14px 4px 4px; }
  .cat-strip .cat-tile__media { width: 38px; height: 38px; }
  .cat-strip .cat-tile__name { font-size: 13px; }
}

/* ---------- страница товара ---------- */

/* В прежней витрине карточка была шторкой поверх каталога и ширину ей задавал
   .sheet__panel. Теперь это отдельная страница, ограничиваем сами. */
.pdp-page .pdp { max-width: 1100px; }

a.variant.is-out { opacity: .55; }
a.variant.is-out .variant__meta { color: var(--text-faint); }

/* ---------- прайс-лист ---------- */

/* Превью раньше подставлял JS в data-src; теперь картинка приходит в разметке
   фоном, поэтому размер задаётся здесь. */
.pl-thumb { background-size: cover; background-position: center; display: block; }
.pl-link { display: inline; }
.pl-link:hover { color: var(--accent); }

/* ---------- мелочи ---------- */

.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;
}

.select form, form.select { display: flex; align-items: center; gap: 6px; }

/* Слово «Сортировка» рядом с селектом — только на широком экране.
   Ширину закрытого <select> браузер берёт по самому длинному варианту списка,
   а не по выбранному. Подписи сортировки стали словами вместо стрелок
   («сначала то, чего много» вместо «остаток ↓», см. sort_select в
   shop/templates/_macros.html) — селект вырос со 162 до 227 px, и строка
   toolbar на 375 px перестала помещаться: страница поехала вбок на 41 px.
   Убираем подпись, а не сжимаем селект: сжатый обрезал бы многоточием сам
   выбранный вариант, ради которого его и читают. Для экранного диктора
   подпись никуда не делась — она в aria-label самого select. */
@media (max-width: 620px) {
  .toolbar .select__label, .brandbar__list .select__label { display: none; }
}

/* Форма поиска в шапке занимает место инпута — раскладка .search рассчитана
   на блок с position: relative, форма ведёт себя так же. */
form.search { position: relative; }

/* ---------- первая отрисовка ---------- */

/* Пока стоит .preload, переходы выключены. Без этого на телефоне при каждой
   загрузке проигрывалась анимация из начального состояния шторки каталога —
   выглядело так, будто каталог сам закрывается. Класс снимается инлайновым
   скриптом в <head>, до первой отрисовки. */
.preload *,
.preload *::before,
.preload *::after {
  transition: none !important;
  animation: none !important;
}

/* ---------- подмена содержимого ---------- */

/* Появляется, только если ответа не оказалось в кэше и он задерживается
   дольше 120 мс. При попадании в кэш перехода не видно вовсе. */
.is-navigating .content { opacity: .6; transition: opacity .12s ease-out; }
.is-navigating::after {
  content: "";
  position: fixed; inset: 0 auto auto 0; z-index: 100;
  height: 2px; width: 100%;
  background: var(--accent);
  transform-origin: 0 50%;
  animation: shop-progress .9s ease-out infinite;
}
@keyframes shop-progress {
  0%   { transform: scaleX(0); opacity: 1; }
  70%  { transform: scaleX(.8); opacity: 1; }
  100% { transform: scaleX(1); opacity: 0; }
}

/* ---------- шторка каталога без JavaScript ---------- */

/* Гамбургер — ссылка на #sidebar, поэтому без скриптов шторку открывает :target.
   Иначе на телефоне при выключенном JS единственным путём в разделы оставались
   ссылки в подвале. С JS клик перехватывается и до :target дело не доходит. */
@media (max-width: 900px) {
  .sidebar:target { transform: none; }
  .sidebar:target ~ .content { pointer-events: none; }
}

/* ---------- прокрутка подборок на десктопе ---------- */

/* Лента подборки прокручивается пальцем на телефоне, а мышью цеплять тонкий
   скроллбар неудобно. Стрелки добавляет скрипт и только там, где есть мышь;
   класс is-scrollable он же ставит, когда ленте действительно есть куда ехать. */
.rail-holder { position: relative; }

.rail-nav {
  position: absolute; top: calc(50% - 40px);
  z-index: 2;
  display: none;
  width: 38px; height: 38px;
  align-items: center; justify-content: center;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: 50%;
  box-shadow: var(--shadow-md);
  color: var(--text);
  cursor: pointer;
  transition: opacity .15s, transform .15s;
}
.rail-holder.is-scrollable .rail-nav { display: flex; }
.rail-nav:hover { transform: scale(1.06); }
.rail-nav:disabled { opacity: 0; pointer-events: none; }
.rail-nav svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2; }
.rail-nav--prev { left: -14px; }
.rail-nav--next { right: -14px; }

/* Скроллбар у ленты прячем: за навигацию отвечают стрелки, а полоса под
   карточками только шумит. Прокрутка колесом и пальцем остаётся. */
.rail-holder.is-scrollable .rail { scrollbar-width: none; }
.rail-holder.is-scrollable .rail::-webkit-scrollbar { display: none; }

/* ---------- пока картинка не приехала ---------- */

/* Браузер печатает alt прямо в рамке, пока файл едет и если он не доехал.
   На экране каталога это выглядит как страница с ошибками: два десятка
   прямоугольников, забитых названиями товаров. Текст остаётся в разметке —
   его читают поисковик и экранный диктор, — но глазами его больше не видно,
   а место под картинку и так зарезервировано width/height. */
img { color: transparent; }

/* Место под картинку резервируется всегда (width/height на <img>) — без этого
   сетка дёргалась бы при каждой подгрузке. Но пустой зарезервированный бокс
   читается как «серый квадрат», поэтому пока картинки нет, показываем
   мерцающий скелет: те же ключевые кадры, что у заглушек витрины.

   Класс снимает скрипт по событию load. Если скрипт не отработал, останется
   обычный фон плитки — то есть ровно прежнее поведение, ничего не сломается. */
.card__media:not(.is-ready),
.cat-tile__media:not(.is-ready) {
  background: linear-gradient(100deg, var(--surface-2) 30%, var(--border) 50%, var(--surface-2) 70%);
  background-size: 220% 100%;
  animation: shimmer 1.25s infinite linear;
}

/* Картинка проявляется, а не возникает рывком поверх мерцания.
   Проявляем только ленивые: первые карточки грузятся немедленно, одна из них —
   элемент, по которому считается LCP, и задерживать её показ анимацией значит
   своими руками портить метрику. */
html.js .card__media img[loading="lazy"],
html.js .cat-tile__media img[loading="lazy"] {
  opacity: 0;
  transition: opacity .22s ease-out;
}
html.js .card__media.is-ready img,
html.js .cat-tile__media.is-ready img { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .card__media:not(.is-ready),
  .cat-tile__media:not(.is-ready) { animation: none; }
  html.js .card__media img, html.js .cat-tile__media img { transition: none; }
}

/* ---------- длинная сетка на телефоне ---------- */

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

   content-visibility: auto отдаёт это браузеру: карточки вне экрана (с
   запасом) он не раскладывает, не рисует и анимации в них не тикает —
   виртуализация списка без единой строки скрипта, с сохранением поиска по
   странице, якорей и индексации. contain-intrinsic-size — рост карточки,
   пока она не отрисована, чтобы полоса прокрутки не прыгала; `auto` после
   первого показа подставляет настоящую высоту. Только в сетке: в лентах на
   главной карточек мало, и там это ничего не сэкономит. */
.grid > .card {
  content-visibility: auto;
  contain-intrinsic-size: auto 300px;
}

/* Превью в строке прайса стало картинкой вместо фона (ради ленивой загрузки),
   поэтому размеры задаём ей, а не контейнеру. */
.pl-thumb img {
  width: 100%; height: 100%;
  object-fit: cover;
  border-radius: inherit;
  display: block;
}

/* ---------- иконки ---------- */

/* Вместо эмодзи — один спрайт (Lucide, лицензия ISC, плюс нарисованная под
   проект ракетка). Причина смены простая: эмодзи рисует шрифт операционной
   системы, поэтому на Windows, Android и iPhone это буквально три разные
   картинки, и ни одну из них нельзя ни перекрасить, ни выровнять по тексту.

   Спрайт лежит отдельным файлом, а не встроен в страницу: он один на весь сайт
   и отдаётся с вечным кэшем по отпечатку сборки. */
.icon {
  width: 1.15em; height: 1.15em;
  flex: none;
  fill: none;
  stroke: currentColor;
  vertical-align: -.16em;
}
.icon--ghost { color: var(--text-faint); opacity: .55; }

/* В крупных заглушках иконка занимает место прежней эмодзи. */
.empty__mark .icon { width: 40px; height: 40px; color: var(--text-faint); }
.empty__mark--good .icon { color: #1f9d55; }
.blank__mark .icon { width: 34px; height: 34px; }

.brand__icon { width: 22px; height: 22px; color: var(--accent); }
.pl-home__icon { width: 20px; height: 20px; color: var(--accent); }
.info-card__icon { width: 26px; height: 26px; color: var(--accent); }

/* Иконка-указатель внутри ссылки: «назад», «дальше», «в новой вкладке».

   Раньше на этих местах стояли юникодные стрелки ← → ↗. Рисует их системный
   шрифт, поэтому на Windows, Android и iPhone это три разные закорючки: у одной
   хвост длиннее буквы, другая сидит выше строки, толщину не подобрать ни под
   какое начертание. Теперь это символы того же спрайта, что и остальные иконки.

   Размер в em, а не в пикселях: иконка тянется за кеглем ссылки и одинаково
   стоит и в мелком тексте, и в крупном. Посадка мельче, чем у обычной .icon
   (-.16em): у шеврона рисунок узкий и высокий, при общей посадке он
   проваливался ниже базовой линии.

   Грабля: зазор задан margin, а НЕ пробелом в разметке. Пробел даёт браузеру
   право перенести строку между текстом и значком — «Смотреть все» осталось бы
   на одной строке, а стрелка уехала на следующую. По той же причине сама
   ссылка не переносится (.icon-link ниже); тексты у таких ссылок короткие,
   переполнить строку им нечем. */
.icon--lead, .icon--trail { width: 1em; height: 1em; vertical-align: -.12em; }
.icon--lead { margin-right: .28em; }
.icon--trail { margin-left: .28em; }

/* Ставится на саму ссылку, а не на иконку. */
.icon-link { white-space: nowrap; }

/* У .pdp-back зазор уже задан gap флексбокса — margin сложился бы с ним и
   шеврон отъехал бы вдвое дальше. Переносить строку там тоже нечему:
   у флекс-элементов переноса нет, .icon-link этой ссылке не нужен. */
.pdp-back .icon--lead { margin-right: 0; }

/* Пустая ячейка картинки: без содержимого коробка была бы пустой. */
.card__media--empty, .cat-tile__media:empty { display: grid; place-items: center; }
.card__media--empty .icon, .cat-tile__media .icon--ghost { width: 34px; height: 34px; }

/* Заглушка превью в прайсе рисовалась эмодзи через ::before — гасим. */
.pl-thumb--empty::before { content: none; }
.pl-thumb--empty {
  background-image: none;
  display: grid; place-items: center;
}

/* ---------- экран «заказ принят» ---------- */

/* Блок центрируется целиком, но текст без ограничения ширины растягивался на
   всю колонку и рвался в случайном месте — из-за этого казался съехавшим.
   Заголовок здесь абзац, а не h1 (первый уровень уже занят формой), поэтому
   отступы задаём явно. */
.order-done .legal__title {
  margin: 10px 0 8px;
  font-weight: 650;
}
.order-done .empty__hint {
  max-width: 40ch;
  margin: 0 auto;
}
.order-done .empty__mark {
  display: flex;
  justify-content: center;
}
.order-done .empty__mark .icon { width: 46px; height: 46px; }

/* ---------- страница товара ---------- */

/* Галерея липкая. В витрине товар открывался шторкой, над которой ничего не
   было, поэтому хватало top: 20px. На странице сверху висит шапка, и картинка
   уезжала под неё — сдвигаем на её высоту. */
.pdp-page .gallery { top: calc(var(--topbar-off) + 16px); }

/* Возврат в раздел: в витрине это был крестик на шторке, здесь — ссылка. */
.pdp-back {
  display: inline-flex; align-items: center; gap: 6px;
  margin: 0 0 10px;
  font-size: 14px; color: var(--text-dim); text-decoration: none;
}
.pdp-back:hover { color: var(--accent); }

/* ---------- хлебные крошки ---------- */

/* Были прибиты вплотную к шапке — добавляем воздух сверху. */
.crumbs { padding-top: 14px; }
.pdp-page .crumbs, .content > .crumbs { padding-top: 16px; }

/* ---------- сброс фильтра брендов ---------- */

/* pl-filters__toggle рассчитан на колонку фильтров прайса; в шапке полосы
   брендов он висел без выравнивания и казался приклеенным к заголовку. */
.brandbar__head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
}
.brandbar__head .pl-filters__toggle {
  flex: none;
  font-size: 13px;
  color: var(--text-faint);
  text-decoration: none;
  white-space: nowrap;
}
.brandbar__head .pl-filters__toggle:hover { color: var(--accent); text-decoration: underline; }

/* ---------- логотип ---------- */

/* Настоящий знак магазина картинкой. Высота задана, ширина считается сама —
   пропорции у логотипа вытянутые, и фиксировать обе стороны значит однажды
   его сплющить. */
.brand__logo {
  display: block;
  height: 26px;
  width: auto;
  max-width: 100%;
}
.footer .brand__logo { height: 30px; }
.pl-home .brand__logo { height: 24px; }

/* Прежние знак и надпись больше не отрисовываются, но правила под них в
   styles.css остались — гасим на случай, если разметку где-то не поправили. */
.brand__mark, .brand__text, .pl-home__mark { display: none; }

/* На узком экране логотип ужимается, а не выдавливает поиск из шапки. */
@media (max-width: 640px) {
  .brand__logo { height: 22px; }
}

/* ---------- личный кабинет ---------- */

.account__note {
  margin: 0 0 18px;
  max-width: 70ch;
  font-size: 14px; color: var(--text-dim);
}
.account__out { margin: 0; }

/* Приглашение дозаполнить профиль: строка с двумя кнопками, без рамки внутри
   рамки — обычный блок над разделами, который легко пропустить мимо глаз.
   Оно и должно быть необязательным. */
.account__invite {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px 18px; flex-wrap: wrap;
  margin: 0 0 18px;
}
.account__invite-text {
  margin: 0; max-width: 60ch;
  font-size: 14px; color: var(--text-dim);
}
.account__invite-acts { display: flex; gap: 8px; flex-wrap: wrap; }

/* Согласие на форме входа: тот же вид, что на оформлении, только воздух свой
   — в форме кабинета поля идут теснее. */
.auth__agree { margin: 4px 0 12px; }

/* Сохранённый адрес доставки: строка и кнопка «забыть», без своей рамки. */
.account__address {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px 14px; flex-wrap: wrap;
  margin-bottom: 18px;
}
.account__address .account__note { margin: 0; }
.account__address form { margin: 0; }

/* Статус заказа цветом: покупателю важно различить «в работе» и «отменён»
   боковым зрением, не вчитываясь. */
.order__status--wait { background: #fdf3e2; color: #9a5b00; }
.order__status--work { background: var(--accent-soft); color: var(--accent); }
.order__status--good { background: #e6f5ec; color: #0f7b3f; }
.order__status--bad  { background: #fdeceb; color: #b4231c; }

/* ================= оформление заказа =================
 *
 * Один экран, две колонки: слева форма — карточка «как получить и оплатить»
 * и карточка контактов с согласиями, под ними одна кнопка; справа — что
 * заказано и почём, липкой панелью. Состав держим перед глазами всё время —
 * на этом этапе человека беспокоит ровно одно: то ли он берёт и сколько это
 * стоит. Шагов с «Далее» и экрана «Проверка» нет (checkout.html).
 */

/* Воздух под шапкой — тот же, что у правовых страниц и кабинета: без него
   «На главную» и заголовок прилипали к шапке. */
.co { max-width: 1080px; padding: 22px 0 48px; }
.co__title { font-size: clamp(24px, 3.4vw, 32px); letter-spacing: -.03em; margin: 14px 0 22px; }

.co__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 360px;
  gap: 22px;
  align-items: start;
}
@media (max-width: 940px) { .co__grid { grid-template-columns: 1fr; } }

/* ---------- карточки формы ---------- */

.co__card {
  padding: 22px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin-bottom: 16px;
  /* Прокрутка к карточке (возврат от провайдера, shop.js) — под шапку,
     а не под неё. */
  scroll-margin-top: calc(var(--topbar-off) + 16px);
}

.co__step { display: flex; align-items: center; gap: 11px; margin-bottom: 18px; }
.co__num {
  display: grid; place-items: center;
  width: 26px; height: 26px; flex: none;
  background: var(--accent); color: #fff;
  border-radius: 50%;
  font-size: 13px; font-weight: 700;
}
.co__h { margin: 0; font-size: 17px; font-weight: 650; letter-spacing: -.015em; }

.co__fields { display: grid; gap: 16px; }

/* ---------- поля ---------- */

.fld { display: block; }
.fld__label { display: block; font-size: 13.5px; font-weight: 550; margin-bottom: 6px; }
.fld__label em { font-style: normal; font-weight: 400; color: var(--text-faint); }

.fld input, .fld textarea, .fld select {
  width: 100%; padding: 11px 13px;
  background: var(--bg);
  border: 1.5px solid var(--border); border-radius: 10px;
  font: inherit; font-size: 15px; color: inherit;
  transition: border-color .13s, background .13s;
}
/* Список — со стрелкой, как сортировка каталога: без неё поле дня курьера
   читалось как надпись, а не как выбор. */
.fld select {
  appearance: none; -webkit-appearance: none; cursor: pointer; padding-right: 40px;
  background: var(--bg) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238a8a96' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E") no-repeat right 13px center / 16px;
}
.fld input::placeholder, .fld textarea::placeholder { color: var(--text-faint); }
.fld input:hover, .fld textarea:hover, .fld select:hover { border-color: var(--border-strong); }
.fld input:focus, .fld textarea:focus, .fld select:focus {
  outline: none;
  background: var(--surface);
  border-color: var(--accent);
}
.fld textarea { resize: vertical; min-height: 76px; }
/* На телефоне поля ровно 16 px — не только по pointer: coarse (см. ниже), но
   и по ширине: узкий экран с мышью (эмуляция, окно в четверть) — тоже
   телефонная раскладка, и зум iOS тут ни при чём, просто так читается. */
@media (max-width: 560px) { .fld input, .fld textarea, .fld select { font-size: 16px; } }

.fld__hint { display: block; margin-top: 6px; font-size: 12.5px; color: var(--text-faint); }

/* Ошибка появляется под полем и только после того, как человек его покинул:
   ругаться на незаполненное поле, пока в нём стоит курсор, — дурной тон. */
.fld__err { display: none; margin-top: 6px; font-size: 12.5px; color: #b4231c; }
.fld.is-bad input, .fld.is-bad textarea { border-color: #d3301f; background: #fff8f7; }
.fld.is-bad .fld__err { display: block; }
.fld.is-bad .fld__hint { display: none; }

/* ---------- способ получения ---------- */

.co__ways { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 16px; }
@media (max-width: 560px) { .co__ways { grid-template-columns: 1fr; } }

.way { display: block; cursor: pointer; }
.way input { position: absolute; opacity: 0; width: 0; height: 0; }
.way__box {
  display: flex; gap: 12px;
  height: 100%; padding: 14px;
  background: var(--bg);
  border: 1.5px solid var(--border); border-radius: 12px;
  transition: border-color .13s, background .13s;
}
.way:hover .way__box { border-color: var(--border-strong); }
.way input:checked + .way__box { border-color: var(--accent); background: var(--accent-soft); }
.way input:focus-visible + .way__box { outline: 2px solid var(--accent); outline-offset: 2px; }

.way__icon { width: 22px; height: 22px; flex: none; color: var(--accent); margin-top: 1px; }
.way__body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.way__body b { font-size: 15px; }
.way__body small { font-size: 12.5px; color: var(--text-dim); }
.way__note { color: var(--text-faint) !important; }

/* ---------- часы работы ---------- */

.co__hours {
  display: flex; gap: 10px;
  padding: 13px 15px; margin-bottom: 16px;
  background: var(--bg); border-radius: 10px;
  font-size: 13px; color: var(--text-dim);
}
.co__hours .icon { width: 17px; height: 17px; flex: none; margin-top: 2px; color: var(--text-faint); }
.co__hours div { display: grid; gap: 3px; }
.co__hours b { font-weight: 550; color: var(--text); }

.co__address { margin-bottom: 16px; }

/* ---------- кнопка отправки ----------
   Одна, под формой, на всю ширину колонки. Над ней — капча (если сервер
   спросил), предупреждение об оферте и ошибка отправки. */

.co__nav { padding: 2px 2px 0; }
.co__next { width: 100%; min-height: 48px; }
.co__error {
  margin: 0 0 12px; padding: 11px 14px;
  background: #fdeceb; border: 1px solid #f3c9c6; border-radius: 10px;
  color: #b4231c; font-size: 14px;
}
/* Предупреждение об оферте — прямо над кнопкой: нажатие должно быть
   осознанным, а значит, текст обязан стоять перед ним. */
.co__legal { margin: 0 0 12px; font-size: 12.5px; color: var(--text-faint); }
.co__legal a { color: var(--text-dim); }

/* ---------- оплата ----------
   Блок стоит в конце шага доставки, после адреса и комментария, и от них
   его отделяет одна черта: не рамка (рамка в карточке — рамка в рамке), а
   граница между «как получить» и «чем платить». Заголовок такой же, как у
   «Чем доставить», — это разделы одного шага, а не два шага. */

.co__pay { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--border); }
.co__ways--pay { margin-bottom: 0; }
/* Три карточки в ряд не встают — две в строке и одна под ними хуже, чем
   столбик: столбик читается сверху вниз как список. */
.co__ways--pay { grid-template-columns: 1fr; gap: 8px; }
.co__ways--pay .way__box { padding: 12px 14px; }

/* Место под виджет SmartCaptcha. Своей рамки нет намеренно: виджет приезжает
   из Яндекса уже в рамке, и вторая вокруг неё была бы рамкой в рамке. Высоту
   заранее не резервируем — блок показывают редко, и пустая полоса под кнопкой
   выглядела бы поломкой; появляется он вместе с текстом «почему спрашиваем»,
   так что скачка на пустом месте не будет. */
.co__captcha-widget { margin: 0 0 14px; }

/* ---------- панель заказа ---------- */

.co__side { position: sticky; top: calc(var(--topbar-off) + 16px); }

/* На телефоне панель стоит над формой, свёрнутая в одну строку —
   раскрытая, она отодвигала бы форму за экран, а вовсе без неё человек не
   мог бы посмотреть, что заказывает, не уходя со страницы. Владелец просил
   «скрывающийся список» — это он и есть (см. «свёрнутая панель» в конце
   файла).

   Сделано без переноса узлов: форма на узком экране «растворяется»
   (display: contents), её дети становятся ячейками сетки, и панель через
   order встаёт первой. Без скрипта сворачивать некому (html.js), и панель
   остаётся после формы — читать состав из localStorage всё равно нечем. */
@media (max-width: 940px) {
  .co__side { position: static; }
  .co__form { display: contents; }
  html.js .co__side { order: -1; }
  /* Расстояния между ячейками задаёт сетка: собственные поля карточек
     сложились бы с её зазором. */
  .co__grid { gap: 16px; }
  .co__card { margin-bottom: 0; }
}

.co__summary {
  padding: 20px;
  background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}
.co__sum-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 14px; }

/* overflow-x hidden: блок сборки под основанием и его список никогда не
   должны двигать панель вширь — длинное название режется многоточием.
   Поле справа — под полосу прокрутки: накладываемая полоса (Windows 11,
   macOS) рисуется поверх содержимого и закрывала цены (владелец: «скролл
   перекрывает цены»); тонкая полоса — чтобы поля хватало и классической. */
.co__items {
  display: grid; gap: 14px; min-width: 0;
  max-height: 46vh; overflow-y: auto; overflow-x: hidden;
  padding-right: 12px; margin-bottom: 16px;
  scrollbar-width: thin;
}
@media (max-width: 940px) { .co__items { max-height: none; padding-right: 0; } }

.co-item { display: grid; grid-template-columns: 52px minmax(0, 1fr); gap: 12px; align-items: start; }
/* Без рамки: фотографии товаров и так квадратные на белом, и рамка вокруг
   них рисовала второй квадрат — картинка услуги без рамки смотрелась лучше,
   владелец это заметил. Скругление то же, что у неё. */
.co-item__media {
  width: 52px; height: 52px;
  display: grid; place-items: center;
  background: var(--tile); border-radius: 10px;
  overflow: hidden;
}
.co-item__media img { width: 100%; height: 100%; object-fit: cover; }
.co-item__body { display: grid; gap: 4px; min-width: 0; }
.co-item__title { font-size: 13.5px; font-weight: 550; line-height: 1.3; }
.co-item__variant { font-size: 12px; color: var(--text-faint); }
.co-item__row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.co-item__sum { font-size: 13.5px; font-weight: 650; font-variant-numeric: tabular-nums; }

/* Количество меняется прямо здесь: возвращать человека в корзину ради
   одной штуки — лишний круг по сайту. */
.co-item__qty { display: flex; align-items: center; gap: 2px; }
.co-item__qty button {
  width: 26px; height: 26px;
  display: grid; place-items: center;
  background: var(--bg); border: 1px solid var(--border); border-radius: 7px;
  font: inherit; font-size: 14px; line-height: 1; color: var(--text-dim); cursor: pointer;
}
.co-item__qty button:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.co-item__qty button:disabled { opacity: .4; cursor: default; }
.co-item__qty span { min-width: 22px; text-align: center; font-size: 13.5px; font-variant-numeric: tabular-nums; }

/* ---------- сборка ракетки под основанием ----------
   Пока услуги нет — «+ Сборка ракетки · 500 ₽» тем же кеглем, что «изменить»:
   строка-ссылка под количеством основания, никакой плитки. Добавили — на её
   месте блок: подпись с ценой и «убрать», на каждую единицу основания два
   ряда друг под другом — forehand и backhand. Это часть строки основания, а
   не вложенная карточка: ни рамки, ни фона — только тонкая линия слева,
   скобкой от миниатюры основания к тому, что на него клеится.

   Ряд — одна кнопка во всю ширину блока: слева ячейка 40 px (пустая —
   пунктир с плюсом, заполненная — миниатюра накладки с кружком цвета в углу,
   тот же .chip__dot, что в карточке товара), справа мелкая подпись стороны и
   под ней название накладки одной строкой с многоточием. Две ячейки в ряд с
   подписями снизу (как было) не вмещали названий, и что куда наклеено,
   читалось только по кружку; вертикально названию есть вся ширина. Список
   накладок раскрывается ПОД рядом, в потоке и не шире блока.

   Блок живёт в двух местах — в сводке оформления (.co-item__asm, на широком
   экране панель ~320 px) и в шторке корзины (.cart-row__asm) — и нигде не
   должен ничего расширять: min-width: 0 у всех flex/grid-детей, overflow
   hidden у самого блока, у списка позиций и у тела шторки. Горизонтальная
   прокрутка страницы — это то, чего владелец просил не допускать нигде. */
.co-item__asm { padding-top: 2px; min-width: 0; }
.co-item__asm-add {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 0; border: 0; background: none;
  font: inherit; font-size: 13px; color: var(--accent); cursor: pointer;
}
.co-item__asm-add .icon { width: 14px; height: 14px; }
.co-item__asm-add:hover { color: var(--text); }

.asm {
  display: grid; gap: 8px;
  min-width: 0; overflow: hidden;
  padding-left: 10px; border-left: 2px solid var(--border);
  font-size: 12.5px; line-height: 1.4;
}
.asm__head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; min-width: 0; }
.asm__title { font-weight: 550; color: var(--text); font-variant-numeric: tabular-nums; }
.asm__unit { display: grid; gap: 6px; min-width: 0; }
.asm__num { font-size: 12px; color: var(--text-faint); }
/* Несколько сборок на одно основание: номер слева, свой «убрать» справа —
   тем же приглушённым текстом, что «убрать» в шапке блока. */
.asm__unit-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; min-width: 0; }
.asm__unit-remove { font-size: 12px; }
.asm__row {
  display: flex; align-items: center; gap: 10px;
  width: 100%; min-width: 0; padding: 0; margin: 0;
  border: 0; border-radius: 10px; background: none;
  font: inherit; color: var(--text); text-align: left; cursor: pointer;
}
.asm__row:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.asm__text { display: grid; gap: 1px; flex: 1; min-width: 0; }
.asm__cap { font-size: 11px; line-height: 1.2; color: var(--text-faint); }
.asm__name {
  display: block; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 12.5px; line-height: 1.3;
}
/* Пустой слот зовёт акцентом — это единственное, что в ряду нужно нажать. */
.asm__row.is-empty .asm__name { color: var(--accent); }
.asm__slot {
  position: relative; flex: none;
  width: 40px; height: 40px;
  display: grid; place-items: center;
  background: var(--tile); border: 1.5px dashed var(--border-strong); border-radius: 10px;
  color: var(--text-faint);
  transition: border-color .13s, color .13s;
}
.asm__slot .icon { width: 18px; height: 18px; }
.asm__row:hover .asm__slot, .asm__row.is-open .asm__slot { border-color: var(--accent); color: var(--accent); }
.asm__row:hover .asm__name { color: var(--accent); }
.asm__row.is-filled .asm__slot { border-style: solid; border-color: var(--border); overflow: hidden; }
.asm__thumb, .asm__opt-img {
  position: relative; display: grid; place-items: center;
  width: 100%; height: 100%; overflow: hidden;
}
.asm__thumb img, .asm__opt-img img { width: 100%; height: 100%; object-fit: cover; }
.asm__thumb .icon--ghost, .asm__opt-img .icon--ghost { width: 18px; height: 18px; }
/* Кружок цвета поверх фотографии, в углу; белая обводка отделяет его от
   красной накладки на снимке. */
.asm__dot { position: absolute; right: 3px; bottom: 3px; width: 10px; height: 10px; box-shadow: 0 0 0 1.5px #fff, inset 0 0 0 1px rgba(0, 0, 0, .22); }
/* Основание без единой накладки после проверки: подсвечены его ячейки и
   подсказка под блоком — тем же красным, что ошибки полей. */
.asm__unit.is-bad .asm__row.is-empty .asm__slot { border-color: #d3301f; color: #d3301f; }
.asm__unit.is-bad .asm__row.is-empty .asm__name { color: #d3301f; }
.asm__hint { margin: 0; font-size: 12.5px; color: #b4231c; }
/* Сборка переставила оплату — строка в самом блоке, где человек только что
   нажал плюсик: тем же тёплым цветом, что строка под панелью (.co__alert),
   без плашки — плашка внутри блока внутри строки была бы третьим контуром. */
.asm__warn { margin: 0; font-size: 12.5px; line-height: 1.4; color: #9a3412; }

/* Список накладок под рядом. В потоке, а не всплывашкой: список позиций
   на широком экране прокручивается внутри себя (.co__items), и всплывашка
   резалась бы его краем. Строка — миниатюра, кружок цвета, «1.8 красный»
   и модель мельче — одной строкой с многоточием, чтобы никогда не выйти
   за ширину; занятый в другом слоте экземпляр приглушён и подписан второй
   строкой — откуда приедет (.asm__opt-note). */
.asm__picker {
  display: grid; gap: 2px; min-width: 0;
  padding: 6px; background: var(--surface-2, var(--bg)); border-radius: 10px;
}
/* Сетка, а не ряд: подпись «перенести сюда…» второй строкой идёт во всю
   ширину пункта, под миниатюрой тоже. В панели заказа (~320 px) это те самые
   35 px, без которых фраза не помещается и режется на полуслове. */
.asm__opt {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  align-items: center; column-gap: 8px; row-gap: 1px;
  width: 100%; min-width: 0; min-height: 34px; padding: 3px 8px 3px 3px;
  border: 0; border-radius: 8px; background: none;
  font: inherit; font-size: 12.5px; color: var(--text); text-align: left; cursor: pointer;
}
.asm__opt:hover:not(:disabled), .asm__opt:focus-visible { background: var(--tile); outline: none; }
/* Выбранный пункт — цветом, а не рамкой: рамка внутри списка внутри строки
   была бы третьим вложенным контуром. */
.asm__opt[aria-selected="true"] { background: var(--tile); color: var(--accent); }
.asm__opt:disabled { opacity: .4; cursor: default; }
.asm__opt-img { width: 28px; height: 28px; border-radius: 7px; background: var(--tile); }
.asm__opt-img .asm__dot { right: 2px; bottom: 2px; width: 9px; height: 9px; }
.asm__opt-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.asm__opt-text b { font-weight: 550; }
.asm__opt-model { color: var(--text-dim); }
/* Накладка уже стоит в другом слоте: сама строка приглушена (она занята),
   под ней — вторая строка мельче, откуда она приедет и что случится с этим
   слотом. Ни значка, ни плашки: пометка объясняет словами, а не настроением,
   и тоже режется многоточием — список не шире блока ни при каком названии
   основания. */
.asm__opt.is-taken .asm__opt-text { color: var(--text-dim); }
.asm__opt.is-taken .asm__opt-img { opacity: .55; }
.asm__opt.is-taken:hover .asm__opt-text, .asm__opt.is-taken:focus-visible .asm__opt-text { color: var(--text); }
.asm__opt.is-taken:hover .asm__opt-img, .asm__opt.is-taken:focus-visible .asm__opt-img { opacity: 1; }
.asm__opt-note {
  grid-column: 1 / -1;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 11px; line-height: 1.25; color: var(--text-dim);
}
.asm__opt--none { min-height: 30px; padding-left: 8px; color: var(--text-dim); }

/* Тот же блок в шторке корзины: под кнопками строки основания, в колонке
   текста (первая — миниатюра). Тело шторки — без горизонтальной прокрутки
   ни при каком названии. */
.cart-row__asm { grid-column: 2; min-width: 0; margin-top: 10px; }
.cart__body { overflow-x: hidden; }
.cart__notes { margin: 0 0 12px; }
.cart__note { margin: 0; font-size: 12px; line-height: 1.4; color: var(--text-dim); }

/* Строка под панелью заказа: сборка переставила оплату. Тёплая плашка без
   рамки — одна на всю панель, поверх белой карточки читается сразу, а
   рамкой не спорит с рамкой самой панели. */
.co__alert {
  margin: 12px 0 0; padding: 9px 12px;
  background: #fff4e5; border-radius: 10px;
  font-size: 12.5px; line-height: 1.4; color: #9a3412;
}
.co__total {
  display: flex; align-items: baseline; justify-content: space-between;
  padding-top: 15px; margin-bottom: 16px;
  border-top: 1px solid var(--border);
  font-size: 14px;
}
.co__total b { font-size: 21px; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
/* Итог — последняя строка панели: кнопки под ним больше нет. */
.co__total:last-child { margin-bottom: 0; }

/* ---------- пусто и готово ---------- */

.co__empty, .co__done {
  padding: 56px 24px; text-align: center;
  background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius);
}
/* Блок несёт и класс empty (checkout.html): колонку, зазоры и отступ кнопки
   даёт он; здесь — только свои поля и рамка. Поля .empty (80 px) перебиваются
   правилом выше: этот файл подключён позже. */

.co__done-title { margin: 12px 0 8px; font-size: clamp(20px, 2.6vw, 26px); font-weight: 650; letter-spacing: -.02em; }
.co__done-text { margin: 0 auto 22px; max-width: 44ch; color: var(--text-dim); font-size: 15px; }
/* Кнопки под итогом: в ряд, пока их немного и они помещаются, столбиком —
   на телефоне. Ширина ограничена: кнопка во весь экран на планшете читается
   как полоса, а не как кнопка. Главная всегда одна (btn--go), остальные
   тихие (btn--ghost) — иначе ряд одинаково ярких кнопок не подсказывает,
   чего от человека ждут. */
.co__done-actions {
  display: flex; gap: 10px; justify-content: center; flex-wrap: wrap;
  margin-inline: auto; max-width: 520px;
}
.co__done-actions .btn { flex: 1 1 200px; max-width: 320px; }
/* На странице заказа ряд стоит прямо под карточкой заказа, а не под текстом
   с его нижним полем, — без своего зазора кнопка прилипала к рамке. */
.orders + .co__done-actions { margin-top: 24px; }
@media (max-width: 560px) {
  .co__done-actions { flex-direction: column; align-items: stretch; max-width: 320px; }
  .co__done-actions .btn { flex: none; max-width: none; }
}

/* Кнопка оплаты у заказа в кабинете: не на всю ширину карточки — это
   действие для одного заказа, а не для страницы. */
.order__pay { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin-top: 12px; }
.order__pay-btn { padding: 9px 18px; font-size: 14px; }
.order__pay-err { font-size: 12.5px; color: #b4231c; }

/* ---------- превью товара ---------- */

/* Название и картинка позиции стали ссылками — иначе превью нельзя было бы
   открыть без скриптов и обойти роботом. Ссылка по умолчанию синяя с
   подчёркиванием, а в корзине это обычный текст строки. */
a.cart-row__title,
a.cart-row__media,
a.co-item__title,
a.co-item__media {
  color: inherit;
  text-decoration: none;
}
a.cart-row__title, a.co-item__title { display: block; }
a.cart-row__title:hover, a.co-item__title:hover { color: var(--accent); }
a.cart-row__media:hover, a.co-item__media:hover { opacity: .85; }
/* Строка корзины (assets/styles.css): та же миниатюра без рамки и без
   внутреннего поля — по одному правилу с оформлением. */
.cart-row__media { border: 0; padding: 0; border-radius: 10px; overflow: hidden; }
.cart-row__media img { object-fit: cover; }

/* Шторка поверх корзины: у корзины z-index 55, превью открывают из неё. */
.preview { position: fixed; inset: 0; z-index: 60; }
.preview__backdrop {
  position: absolute; inset: 0;
  background: rgba(10, 10, 14, .5);
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
  animation: fade .2s var(--ease);
}
.preview__panel {
  position: absolute; inset: 0 0 0 auto;
  width: min(560px, 100%);
  display: flex; flex-direction: column;
  background: var(--surface);
  box-shadow: var(--shadow-lg);
  overflow-y: auto; overscroll-behavior: contain;
  animation: slide-in .28s var(--ease);
}
/* Крестик стоит своей строкой, а не поверх содержимого: панель узкая, и
   наложение неминуемо накрыло бы бренд или первый вариант. */
.preview__close {
  position: sticky; top: 12px; align-self: flex-end; flex: none; z-index: 2;
  margin: 12px 12px 0 0;
  background: var(--surface); border: 1px solid var(--border);
  box-shadow: var(--shadow-sm);
}
.preview__body { padding: 6px clamp(16px, 3vw, 26px) 28px; }

/* Пока фрагмент едет, место под ним не должно схлопываться: иначе шторка
   открывается пустой полоской и тут же прыгает на полный рост. */
.preview.is-loading .preview__body::after {
  content: ""; display: block; height: 260px;
  border-radius: var(--radius); background: var(--surface-2);
  animation: pv-pulse 1.1s ease-in-out infinite;
}
@keyframes pv-pulse { 50% { opacity: .45; } }

.pv { display: grid; gap: 18px; }
.pv__gallery { display: grid; gap: 8px; }
.pv__stage {
  aspect-ratio: 1;
  background: var(--tile);
  border: 1px solid var(--border); border-radius: var(--radius);
  overflow: hidden;
}
.pv__stage--empty { display: grid; place-items: center; }
.pv__photo { width: 100%; height: 100%; object-fit: contain; padding: 8px; }
.pv__thumbs { display: flex; gap: 6px; flex-wrap: wrap; }
.pv__thumb {
  width: 52px; height: 52px; padding: 3px;
  background: var(--tile); border: 1.5px solid var(--border); border-radius: 8px;
  cursor: pointer; overflow: hidden;
}
.pv__thumb img { width: 100%; height: 100%; object-fit: contain; }
.pv__thumb.is-active { border-color: var(--accent); }

.pv__info { display: grid; gap: 12px; align-content: start; min-width: 0; }
.pv__tags { display: flex; flex-wrap: wrap; gap: 6px; }
.pv__tag {
  padding: 3px 9px;
  background: var(--surface-2); border-radius: 999px;
  font-size: 12px; font-weight: 600; color: var(--text-dim);
}
.pv__tag--plain { background: none; padding-left: 0; font-weight: 500; color: var(--text-faint); }
.pv__title { margin: 0; font-size: 19px; line-height: 1.25; letter-spacing: -.02em; }
.pv__price-row { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.pv__price { font-size: 23px; font-weight: 700; letter-spacing: -.03em; font-variant-numeric: tabular-nums; }
.pv__note { font-size: 12.5px; color: var(--text-faint); }

.pv__variants { display: flex; flex-wrap: wrap; gap: 8px; }
/* .variant рассчитан на ссылку; здесь это кнопка, а она не наследует шрифт */
.pv .variant { font: inherit; }
.pv .variant.is-out { opacity: .55; }
.pv .variant.is-out .variant__meta { color: var(--text-faint); }

.pv__buy { display: flex; align-items: center; gap: 12px; min-height: 44px; }
.pv__add { flex: 1; }
.pv__add:disabled { background: var(--surface-2); color: var(--text-faint); cursor: default; filter: none; }
.pv__stepper { height: 40px; }
.pv__stepper .stepper__btn { width: 40px; height: 38px; font-size: 17px; }

.pv__desc { margin: 0; font-size: 13.5px; line-height: 1.6; color: var(--text-dim); }
.pv__full { font-size: 13.5px; font-weight: 600; color: var(--accent); text-decoration: none; }
.pv__full:hover { text-decoration: underline; }

/* Панель узкая, поэтому фото и текст встают в две колонки только когда места
   действительно хватает. */
@media (min-width: 780px) {
  .pv { grid-template-columns: 210px minmax(0, 1fr); align-items: start; gap: 20px; }
}

/* На телефоне превью занимает весь экран и выезжает снизу — как и корзина. */
@media (max-width: 700px) {
  .preview__panel { inset: 0; width: 100%; animation: slide-up .28s var(--ease); }
  .preview__close {
    top: calc(10px + env(safe-area-inset-top, 0px));
    margin-right: 10px;
    width: 40px; height: 40px; border-radius: 50%;
  }
  .preview__body { padding-bottom: calc(28px + env(safe-area-inset-bottom, 0px)); }
  .pv__thumbs { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
  .pv__thumbs::-webkit-scrollbar { display: none; }
  .pv__thumb { flex: none; }
}

/* ---------- уведомление о хранении данных ----------

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

   position: fixed не ради вида: элемент выпадает из потока, поэтому снятие
   hidden скриптом не двигает вёрстку. Скрипт с defer успевает до первой
   отрисовки — скачков нет вовсе. */
.cookies {
  position: fixed;
  /* выше содержимого, но ниже затемнения (34), шторки каталога (35),
     корзины (50) и лайтбокса (60): открытое поверх всегда важнее */
  z-index: 32;
  right: var(--gutter);
  bottom: max(var(--gutter), env(safe-area-inset-bottom, 0px));
  max-width: min(400px, calc(100vw - var(--gutter) * 2));
  display: flex; align-items: center; gap: 14px;
  padding: 12px 12px 12px 16px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: 14px;
  box-shadow: 0 10px 30px -10px rgb(0 0 0 / .28), 0 2px 8px rgb(0 0 0 / .08);
  animation: cookies-in .26s var(--ease) both;
}

.cookies__text {
  margin: 0; min-width: 0;
  font-size: 13.5px; line-height: 1.35; color: var(--text);
}
.cookies__text a { color: var(--accent); text-underline-offset: 2px; }
.cookies__text a:hover { text-decoration: none; }

.cookies__ok {
  flex: none;
  padding: 8px 16px;
  background: var(--accent); color: #fff;
  border: 0; border-radius: 9px;
  font: inherit; font-size: 13.5px; font-weight: 600; line-height: 1;
  cursor: pointer;
  transition: filter .13s, transform .08s;
}
.cookies__ok:hover { filter: brightness(1.08); }
.cookies__ok:active { transform: scale(.96); }
.cookies__ok:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

@keyframes cookies-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) { .cookies { animation: none; } }

/* ---------- полоса «мы обновили сайт» ----------
 *
 * В потоке над первым экраном главной, а не поверх страницы: переезд стоит
 * одной спокойной строки, а не окна, которое надо закрыть, чтобы увидеть
 * товар. Своей рамки нет — мягкая подложка на белом фоне страницы отделяет
 * полосу и не заводит рамку внутри рамки.
 *
 * Живёт эта полоса несколько месяцев после переключения домена, потом её
 * удаляют вместе с разметкой и notice.js — тремя файлами, одним куском. */
/* Полоса «добро пожаловать на обновлённый сайт» на главной, между
   приветствием и плитками разделов. Тон — спокойная новость: серая подложка
   (тонированная акцентом читалась как тревога) и ни одной залитой кнопки —
   «Что поменялось» ссылкой цвета акцента, «Понятно» тихой кнопкой без рамки
   (рамка внутри плашки дала бы вложенную рамку).

   Ширину полосы решает не окно, а колонка рядом с меню каталога, поэтому
   перенос — на flex-wrap, а не на @media: тексту нужно не меньше 340 px, не
   влезает рядом с кнопками — кнопки уходят строкой под текст. */
.notice {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 18px;
  margin: 0 0 22px;
  padding: 12px 10px 12px 18px;
  background: var(--surface-2);
  border-radius: 14px;
}
.notice__text {
  flex: 1 1 340px; min-width: 0; margin: 0;
  font-size: 14.5px; line-height: 1.45; color: var(--text);
}
.notice__actions { flex: none; display: flex; align-items: center; gap: 2px; }
.notice__more,
.notice__ok {
  padding: 8px 12px;
  border-radius: var(--radius-sm, 9px);
  font: inherit; font-size: 14px; font-weight: 600; line-height: 1.2;
  white-space: nowrap;
  transition: background .13s, color .13s;
}
.notice__more { color: var(--accent); text-decoration: none; }
.notice__ok {
  background: transparent; color: var(--text-dim);
  border: 0; cursor: pointer;
}
.notice__more:hover,
.notice__ok:hover { background: var(--surface); }
.notice__ok:hover { color: var(--text); }
.notice__more:focus-visible,
.notice__ok:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

@media (max-width: 560px) {
  /* Телефон: кнопки строкой под текстом. Без отрицательного отступа:
     выровненная «буква в букву» по тексту ссылка прижималась к краю плашки. */
  .notice { padding: 14px 12px 10px 16px; gap: 10px; }
  .notice__text { flex-basis: 100%; font-size: 14px; }
  .notice__actions { flex-basis: 100%; gap: 8px; }
}

/* На телефоне карточка товара держит внизу свою панель с ценой и кнопкой.
   Раньше расступалось уведомление: оно приподнималось на 74 px — высоту той
   панели, подобранную на глаз. Теперь наоборот, и по двум причинам. Число было
   враньём: панель со счётчиком и кнопкой «Оформить» выше, а от правки текста в
   баннере подгонка разъехалась бы совсем. И порядок неверный: уведомление
   уходит по одному нажатию и живёт минуты, а панель покупки — постоянная часть
   страницы; двигаться должна та полоса, что оказалась соседкой временно.

   Теперь высоту занятого низа сообщает сама полоса про куки (cookies.js,
   --bottom-inset), а панель покупки от неё отсчитывается.

   На планшете (600–900 px) полоса прижата к левому краю и не шире 520 px:
   растянутая от края до края, как на телефоне, она на 768 px превращалась
   в пустую доску с двумя словами слева и кнопкой справа. На телефоне
   (уже 600 px) — во всю ширину, иначе текст ломается на три строки. */
@media (max-width: 900px) {
  .cookies {
    left: var(--gutter);
    right: auto;
    max-width: 520px;
  }
}
@media (max-width: 599px) {
  .cookies {
    right: var(--gutter);
    max-width: none;
  }
}

/* ---------- хлебные крошки: воздух под шапкой ---------- */

/* Крошки — первый элемент страницы, и воздуха над ними должно быть столько же,
   сколько получает первый элемент страницы без крошек: .home, .main, .legal и
   .account начинаются с 26px. Прежние 16px делали строку заметно ближе к шапке,
   чем всё остальное на сайте, — отсюда и ощущение «прилипла».

   Перечисление селекторов нужно ровно для того, чтобы перебить два ранних
   правила (.crumbs, .pdp-page .crumbs, .content > .crumbs): они опирались на
   обёртку, причём .pdp-page .crumbs не совпадал никогда — .pdp-page висит на
   <main> ниже крошек, а не выше. Голый .crumbs в списке — общий случай: он
   сработает в любой обёртке, куда крошки положат потом. */
.crumbs,
.pdp-page .crumbs,
.content > .crumbs {
  padding: 26px 0 12px;
}

/* Крошки относятся к содержимому под собой, а не к шапке над собой, поэтому
   весь верхний отступ отдан им, а следующий блок начинается сразу. Селектор
   «что бы ни шло следом» — чтобы не перечислять .main, .legal и .pricelist. */
.crumbs + * { padding-top: 0; }

/* Шапка липкая и накрывает верх страницы. Без этого «Перейти к содержимому»
   и якорные переходы ставят крошки ровно под шапку, и их не видно. */
.crumbs,
#content { scroll-margin-top: var(--topbar-off); }

/* На прайсе шапки и бокового каталога нет, а таблица центрируется по 980px.
   Крошки лежат выше неё и во всю ширину — без этого они уезжают левее всего
   остального содержимого страницы. */
body.is-price .crumbs { max-width: 980px; margin-inline: auto; }

/* ---------- подсказки адреса ---------- */

/* Разметку под этот список задал ещё старый макет в viewer/styles.css: там
   .address__suggest абсолютный, а держала его обёртка .address с
   position: relative. На новом оформлении обёртки не стало, и список поехал
   отсчитываться от края документа — отсюда и «подсказки на всю ширину
   страницы». Возвращаем обёртку, но уже вокруг одного поля, а не всего блока:
   тогда ширина списка равна ширине поля, чего от него и ждут. */
.address { display: block; position: relative; }

.co__address .address__suggest,
.co__city .address__suggest {
  position: absolute; top: calc(100% + 6px); left: 0; right: 0;
  z-index: 30;
  margin: 0;
  max-height: 264px; overflow-y: auto;
  padding: 4px;
  background: var(--surface);
  border: 1px solid var(--border-strong); border-radius: 12px;
  box-shadow: 0 12px 32px rgba(15, 18, 25, .16);
}

.co__address .address__option,
.co__city .address__option {
  display: block; width: 100%;
  padding: 9px 11px;
  border: 0; border-radius: 8px;
  background: none;
  font: inherit; font-size: 14px; color: inherit;
  text-align: left; cursor: pointer;
}
/* Наведение и стрелки подсвечивают одинаково: иначе на клавиатуре не видно,
   что именно выберет Enter. */
.co__address .address__option:hover,
.co__address .address__option.is-active,
.co__city .address__option:hover,
.co__city .address__option.is-active { background: var(--accent-soft); color: var(--accent); }
.co__address .address__option:focus-visible,
.co__city .address__option:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.address__maplink {
  display: inline-block; margin-top: 8px;
  font-size: 12.5px; color: var(--text-dim);
}

/* ---------- варианты доставки ---------- */

.co__ship { margin-bottom: 16px; }
.co__ship-title { margin: 0 0 8px; font-size: 13.5px; font-weight: 550; }
.co__ship-list { display: grid; gap: 8px; }

.ship { display: block; cursor: pointer; }
.ship input { position: absolute; opacity: 0; width: 0; height: 0; }
.ship__box {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: 2px 12px;
  padding: 12px 14px;
  background: var(--bg);
  border: 1.5px solid var(--border); border-radius: 10px;
  transition: border-color .13s, background .13s;
}
.ship__box b { font-size: 14.5px; font-weight: 550; }
.ship__box small { grid-column: 1 / -1; font-size: 12.5px; color: var(--text-dim); }
.ship:hover .ship__box { border-color: var(--border-strong); }
.ship input:checked + .ship__box { border-color: var(--accent); background: var(--accent-soft); }
.ship input:focus-visible + .ship__box { outline: 2px solid var(--accent); outline-offset: 2px; }

.ship__price { font-size: 14px; font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Неизвестная цена не должна выглядеть как цена: серым и мелким, чтобы её не
   приняли за сумму, которую мы обещаем. */
.ship__price--unknown { font-size: 12.5px; color: var(--text-faint); white-space: normal; }

.co__ship-note { margin: 8px 0 0; font-size: 12.5px; color: var(--text-faint); }

/* Пометка «подставили доставку из прошлого заказа»: спокойная строка между
   городом и вариантами, без рамки и фона — это сообщение, а не плашка. */
.co__prefill { margin: 0 0 16px; font-size: 13px; color: var(--text-dim); }
/* «Изменить» внутри пометки — кнопка, но выглядит текстом: она часть фразы,
   а не отдельный элемент управления. */
.co__prefill-btn {
  padding: 0; border: 0; background: none;
  font: inherit; color: var(--accent); cursor: pointer;
  text-decoration: underline; text-underline-offset: 2px;
}
.co__prefill-btn:hover { color: var(--text); }
.co__prefill-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }

/* Подсказки города под полем: пометка «определили по сети» стоит вплотную к
   полю, как подпись, а кнопка «Определить мой город» — той же строкой, что
   «Открыть в Яндекс.Картах» у адреса (.address__maplink): тихая, серая, со
   значком. Это второстепенный путь, и кричать ему не о чем. */
.co__geo-note { margin: 8px 0 0; }
.co__geobtn {
  display: inline-flex; align-items: center; margin-top: 8px;
  padding: 0; border: 0; background: none;
  font: inherit; font-size: 12.5px; color: var(--text-dim); cursor: pointer;
}
.co__geobtn:hover { color: var(--accent); }
.co__geobtn:disabled { color: var(--text-faint); cursor: default; }
.co__geobtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }

/* ---------- город и пункты выдачи СДЭК ----------

   Пункты — список, а не карточки в карточках: рамка есть только у строки, и
   больше ни одной вложенной. Блок целиком лежит между вариантами доставки и
   адресом, поэтому своей рамки у него нет вовсе — заголовок и список. */

.co__city { margin-bottom: 16px; }
.co__points { margin-bottom: 16px; }
.co__points-title { margin: 0 0 8px; font-size: 13.5px; font-weight: 550; }

/* Поиск с подсказками под полем (только в виде «На карте», до пяти строк):
   выпадашка лежит поверх карты, поэтому z-index выше её кнопок. */
.co__points-search { position: relative; margin-bottom: 10px; }
.co__points-find { margin: 0; }
.co__points-hits {
  position: absolute; left: 0; right: 0; top: 100%; z-index: 8;
  margin-top: 4px; padding: 4px;
  background: var(--surface); border-radius: 10px;
  box-shadow: 0 6px 24px rgba(15, 18, 25, .16);
}
.co__points-hits button {
  display: block; width: 100%;
  padding: 8px 10px;
  border: 0; border-radius: 8px;
  background: none; color: inherit;
  font: inherit; font-size: 13.5px; text-align: left;
  cursor: pointer;
}
.co__points-hits button small { display: block; font-size: 12px; color: var(--text-dim); }
.co__points-hits button:hover,
.co__points-hits button:focus-visible { background: var(--surface-2); outline: none; }

/* Переключатель «На карте / Списком» — два вида на один выбор. Сегмент —
   плашка на сером без рамки, активная кнопка белая. Есть только с ключом
   карты и когда карта поднялась; без неё список единственный, и переключать
   нечего (paintPointsView в shop.js). */
.co__points-view {
  display: grid; grid-template-columns: 1fr 1fr; gap: 2px;
  width: max-content; min-width: 240px;
  margin: 0 0 10px; padding: 3px;
  background: var(--surface-2); border-radius: 10px;
}
@media (max-width: 560px) { .co__points-view { width: auto; } }
.co__points-view button {
  padding: 7px 14px;
  border: 0; border-radius: 8px;
  background: none; color: var(--text-dim);
  font: inherit; font-size: 13.5px; font-weight: 550;
  cursor: pointer;
}
.co__points-view button[aria-pressed="true"] {
  background: var(--surface); color: var(--text);
  box-shadow: 0 1px 3px rgba(15, 18, 25, .12);
  cursor: default;
}
.co__points-view button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Тип пункта — чипом: «Постамат» словом, а не значком, который пришлось бы
   объяснять. В списке чип только у постаматов (пункт — норма), в панели под
   картой — у всех. */
.point-chip {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 100px;
  background: var(--accent-soft); color: var(--accent);
  font-size: 11.5px; font-weight: 550; line-height: 1.4;
  white-space: nowrap;
}

/* Список прокручивается внутри себя: в Москве пунктов сотни, и растягивать
   ими страницу — значит потерять и итог заказа, и кнопку отправки. Строка
   короткая: адрес и чип в первой строке, часы и «как найти» — одной второй. */
.co__points-list {
  display: grid; gap: 6px;
  max-height: 320px; overflow-y: auto;
  padding: 2px;
  overscroll-behavior: contain;
}

.point { display: block; cursor: pointer; }
.point input { position: absolute; opacity: 0; width: 0; height: 0; }
.point__box {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 2px 8px;
  align-items: start;
  padding: 9px 12px;
  background: var(--bg);
  border: 1.5px solid var(--border); border-radius: 10px;
  transition: border-color .13s, background .13s;
}
.point__box b { font-size: 13.5px; font-weight: 550; }
.point__box small { grid-column: 1 / -1; font-size: 12px; color: var(--text-dim); }
.point__note { color: var(--text-faint) !important; }
.point__warn { color: #b4231c !important; }
.point:hover .point__box { border-color: var(--border-strong); }
.point input:checked + .point__box { border-color: var(--accent); background: var(--accent-soft); }
.point input:focus-visible + .point__box { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Закрытый пункт (постамат без наложенного платежа при выбранном наложенном):
   приглушён, но читается — человек должен понять, почему и что сделать. */
.point--off { cursor: default; }
.point--off .point__box { opacity: .55; }
.point--off:hover .point__box { border-color: var(--border); }

.co__points-rate { margin: -2px 0 10px; font-size: 13px; color: var(--text-dim); }
.co__points-more,
.co__points-chosen { margin: 8px 0 0; font-size: 12.5px; color: var(--text-faint); }
.co__points-chosen:empty { display: none; }
.co__points-err { margin: 8px 0 0; font-size: 12.5px; color: #b4231c; }

/* ---------- карта пунктов ----------

   Главный вид на пункты (список — по переключателю), той же ширины, что и
   поля, со скруглением полей. Высота — чтобы город читался: 420 на широком
   экране, на телефоне около половины экрана (не меньше 320), и там же ещё
   «Развернуть» на весь экран. Метки, кластеры, кнопки зума — обычные
   элементы страницы поверх карты, свои по стилю; яндексовских балунов и
   пакетов здесь нет (почему — в shop.js). Панель пункта — не поверх, а под
   картой (.point-card ниже). */
.co__points-map {
  position: relative;
  border-radius: 10px; overflow: hidden;
  border: 1.5px solid var(--border);
  background: var(--surface-2);
}
/* contain: inline-size — ширину холста задаёт колонка, а не содержимое: на
   весь экран Яндекс пишет своему контейнеру width: 375px строкой, и после
   «Свернуть» эта строка распирала карточку оформления, карта торчала за
   правый край экрана, а своего ResizeObserver Яндекс так и не дожидался —
   контейнер ведь и правда оставался широким. */
.co__points-canvas { height: 420px; contain: inline-size; }
@media (max-width: 560px) { .co__points-canvas { height: clamp(320px, 52vh, 480px); } }
/* Пока скрипт Яндекса едет, серый прямоугольник говорит, чего ждёт; карта,
   поднявшись, кладёт внутрь свои элементы, и надпись уходит сама. */
.co__points-canvas:empty { display: grid; place-items: center; color: var(--text-faint); font-size: 13px; }
.co__points-canvas:empty::before { content: 'Загружаем карту…'; }

/* Метка пункта — кружок с белой обводкой, якорем в центре: элемент карта
   ставит верхним левым углом в координату, сдвиг — наше дело. Постамат —
   полый кружок; выбранный — крупнее и тёмный; закрытый — серый. */
.pmark {
  position: absolute; left: 0; top: 0;
  width: 16px; height: 16px;
  transform: translate(-50%, -50%);
  padding: 0;
  border: 2.5px solid #fff; border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 1px 4px rgba(15, 18, 25, .35);
  cursor: pointer;
}
.pmark--postamat { background: #fff; border-color: var(--accent); box-shadow: 0 1px 4px rgba(15, 18, 25, .35), inset 0 0 0 2px #fff; }
.pmark--on {
  width: 24px; height: 24px;
  background: var(--text); border-color: #fff;
  z-index: 2;
}
.pmark--postamat.pmark--on { background: #fff; border-color: var(--text); }
.pmark--off { background: var(--text-faint); border-color: #fff; }
.pmark--postamat.pmark--off { background: #fff; border-color: var(--text-faint); }
.pmark:hover { transform: translate(-50%, -50%) scale(1.25); }
.pmark:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.pcluster {
  position: absolute; left: 0; top: 0;
  min-width: 34px; height: 34px;
  transform: translate(-50%, -50%);
  padding: 0 8px;
  border: 2.5px solid #fff; border-radius: 100px;
  background: var(--accent); color: #fff;
  font: inherit; font-size: 13px; font-weight: 600;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 1px 4px rgba(15, 18, 25, .35);
  cursor: pointer;
}
.pcluster:hover { filter: brightness(1.07); }
.pcluster:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Кнопки зума и «Развернуть» — поверх карты, в углах. */
.co__points-zoom {
  position: absolute; right: 10px; top: 10px; z-index: 5;
  display: grid; gap: 4px;
}
.co__points-zoom button,
.co__points-full {
  width: 34px; height: 34px;
  padding: 0;
  border: 0; border-radius: 8px;
  background: var(--surface); color: var(--text);
  box-shadow: 0 1px 4px rgba(15, 18, 25, .25);
  font: inherit; font-size: 18px; line-height: 1;
  cursor: pointer;
}
.co__points-zoom button:hover,
.co__points-full:hover { background: var(--surface-2); }
.co__points-zoom button:focus-visible,
.co__points-full:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.co__points-full {
  position: absolute; left: 10px; top: 10px; z-index: 5;
  width: auto; padding: 0 12px;
  font-size: 13px; font-weight: 550;
}
/* На широком экране карта и так на виду — разворачивать нечего. */
@media (min-width: 561px) { .co__points-full { display: none; } }

/* Карта на весь экран (телефон): тот же блок, только поверх всего. Прокрутку
   страницы под ним замораживаем, иначе карта уезжает вместе с ней. */
.co__points-map.is-full {
  position: fixed; inset: 0; z-index: 90;
  margin: 0; border: 0; border-radius: 0;
}
.co__points-map.is-full .co__points-canvas { height: 100%; }
html.is-map-full, html.is-map-full body { overflow: hidden; }

/* Панель пункта — под картой, а не поверх: поверх она на телефоне закрывала
   половину карты. Серая плашка без рамки (рамка есть у карты, вторая рядом
   была бы лишней): адрес крупно и тип чипом, часы, «как найти» серым, цена
   и срок, «Выбрать здесь» на всю ширину — тот же выбор, что радиокнопка. */
.point-card {
  position: relative;
  display: grid; gap: 3px;
  margin-top: 10px;
  padding: 12px 14px;
  background: var(--surface-2);
  border-radius: 10px;
  /* Панель подкручивается в кадр по клику на метку (scrollIntoView в
     showPointCard): запас снизу — под полосу куки (--bottom-inset ставит
     cookies.js), иначе кнопка «Выбрать здесь» приезжала ровно под неё;
     сверху — под липкую шапку. */
  scroll-margin-top: 72px;
  scroll-margin-bottom: calc(var(--bottom-inset) + 12px);
}
.point-card__head {
  display: flex; flex-wrap: wrap; gap: 4px 8px; align-items: center;
  padding-right: 28px;   /* место под крестик */
}
.point-card__addr { font-size: 15px; font-weight: 550; }
.point-card small { font-size: 12.5px; color: var(--text-dim); }
.point-card__rate { color: var(--text) !important; font-weight: 550; }
.point-card__warn { color: #b4231c !important; }
.point-card__pick { margin-top: 8px; padding: 11px 16px; font-size: 14px; text-align: center; }
.point-card__pick:disabled { opacity: .55; cursor: default; filter: none; }
/* Состояние «выбрано» той же панели: акцентная рамка и чип вместо кнопки. */
.point-card.is-chosen { border-color: var(--accent); }
.point-chip--on { background: var(--accent-soft); color: var(--accent); }
.point-card__close {
  position: absolute; right: 6px; top: 6px;
  width: 28px; height: 28px;
  padding: 0; border: 0; border-radius: 8px;
  background: none; color: var(--text-dim);
  font: inherit; font-size: 20px; line-height: 1;
  cursor: pointer;
}
.point-card__close:hover { background: var(--surface-2); color: var(--text); }
.point-card__close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Карта на весь экран — единственное место, где панель ложится поверх
   карты: низа страницы там нет. Нижняя шторка с «ручкой», выше карты
   (z-index 90 у .is-full), с отступом под вырез экрана; закрывается
   крестиком, Escape и свайпом вниз (shop.js). Панель — сосед карты, а не её
   потомок, поэтому fixed считается от окна, как и у самой карты. */
html.is-map-full .point-card {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 95;
  margin: 0;
  padding: 20px 16px calc(16px + env(safe-area-inset-bottom, 0px));
  max-height: 70vh; overflow-y: auto;
  background: var(--surface);
  border-radius: 16px 16px 0 0;
  box-shadow: 0 -8px 32px rgba(15, 18, 25, .22);
}
html.is-map-full .point-card::before {
  content: '';
  position: absolute; left: 50%; top: 7px;
  width: 36px; height: 4px; margin-left: -18px;
  border-radius: 2px; background: var(--border-strong);
}
html.is-map-full .point-card__close { top: 12px; right: 10px; }

/* После выбора вместо панели — строка «Выбран: адрес · часы» и «изменить»,
   которое снова наводит карту на пункт и открывает панель. */

/* Строки доставки и комиссии над итогом: без верхней линии, они принадлежат
   итогу. Неизвестная цена доставки — словами и не жирно, чтобы её не приняли
   за сумму. */
.co__total--ship { border-top: 0; padding-top: 0; margin-bottom: 8px; }
.co__total--ship b { font-size: 14px; }
.co__total--ship span { padding-right: 12px; }
.co__total-unknown { font-weight: 450; color: var(--text-faint); text-align: right; }

/* «Забыли пароль?» под полем пароля — ссылка-действие, не кнопка. */
.auth__forgot {
  display: block; margin: -6px 0 4px; padding: 0;
  background: none; border: 0;
  font: inherit; font-size: 12.5px;
  color: var(--text-dim); cursor: pointer;
  text-decoration: underline; text-underline-offset: 3px;
}
.auth__forgot:hover { color: var(--accent); text-decoration: none; }

/* Переключатели «войти с паролем / по письму / выслать ещё раз» ПОД главной
   кнопкой: та же тихая ссылка, но отрицательный отступ «забыли пароля»
   (он прижимает ссылку к полю над ней) здесь давил бы их на кнопку. */
.auth__switch { margin: 10px 0 0; }

/* ---------- предложение сборки ---------- */

/* Одна строка в панели заказа, а не баннер: это подсказка к тому, что человек
   уже выбрал, и перебивать ею состав заказа незачем. */
.co__addon {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 4px 10px; align-items: start;
  margin-top: 14px; padding: 11px 12px;
  background: var(--bg); border: 1px dashed var(--border-strong); border-radius: 10px;
}
.co__addon-icon { width: 17px; height: 17px; margin-top: 2px; color: var(--text-faint); }
.co__addon-text { margin: 0; font-size: 12.5px; line-height: 1.45; color: var(--text-dim); }
.co__addon-text b { color: var(--text); font-weight: 550; }
.co__addon-text a { color: var(--accent); text-decoration: underline; }
.co__addon-text small { display: block; margin-top: 3px; font-size: 11.5px; color: var(--text-faint); }

.co__addon-go {
  display: inline; padding: 0; margin: 0;
  background: none; border: 0;
  font: inherit; color: var(--accent); text-decoration: underline; cursor: pointer;
}
.co__addon-close {
  padding: 0 2px; background: none; border: 0;
  font-size: 17px; line-height: 1; color: var(--text-faint); cursor: pointer;
}
.co__addon-close:hover { color: var(--text); }

/* Неизвестная цена — длинная фраза, и в узкой колонке справа она рвётся на
   четыре строки. Отдаём ей строку целиком: заголовок варианта важнее. */
.ship__price--unknown { grid-column: 1 / -1; }

/* ---------- согласие галочкой ----------
   Текст длинный, поэтому строится сеткой: квадратик в своей колонке, текст в
   своей. Через inline-элементы вторая строка подползала бы под квадратик.
   Сам чекбокс рисуем свой: системный не умеет менять размер и цвет, а рядом с
   мелким текстом он выглядит великаном. */
/* Согласия — в конце карточки контактов, под полями (checkout.html):
   отступ сверху отделяет их от подсказки последнего поля. */
.co__agree { display: grid; gap: 12px; margin: 20px 0 0; }

.agree {
  display: grid;
  grid-template-columns: 20px minmax(0, 1fr);
  gap: 10px;
  align-items: start;
  cursor: pointer;
}

.agree .agree__box {
  appearance: none; -webkit-appearance: none;
  width: 20px; height: 20px; min-width: 0; padding: 0; margin: 0;
  flex: none;
  background: var(--bg);
  border: 1.5px solid var(--border-strong);
  border-radius: 6px;
  cursor: pointer;
  transition: background .13s, border-color .13s;
  /* выравниваем по первой строке текста, а не по её верху */
  margin-top: 1px;
}
.agree:hover .agree__box, .agree .agree__box:hover { border-color: var(--accent); }
.agree .agree__box:checked {
  background: var(--accent);
  border-color: var(--accent);
  /* галочка нарисована фоном: своя <svg> внутри input невозможна */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7'/%3E%3C/svg%3E");
  background-size: 14px; background-position: center; background-repeat: no-repeat;
}
.agree .agree__box:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.agree__text {
  font-size: 12.5px; line-height: 1.5; color: var(--text-dim);
}
.agree__text a { color: var(--text); text-underline-offset: 2px; }
.agree__text a:hover { color: var(--accent); }

/* Ошибка встаёт под текстом, во вторую колонку: под квадратиком ей тесно. */
.agree__err { grid-column: 2; }
.agree.is-bad .agree__box { border-color: #d3301f; background: #fff8f7; }
.agree.is-bad .agree__box:checked { background: var(--accent); }

/* ---------- услуги к заказу ----------
   Плитки-предложения в сводке больше нет: она делила сводку пополам и
   читалась как баннер; «+ Сборка ракетки» живёт в строке основания (выше,
   «сборка ракетки под основанием»). Здесь — одна приглушённая строка
   пояснения над итогом, и только пока сборка в заказе. Отступ снизу — те же
   16 px, которыми список позиций отделён от итога. */
.co__notes { margin: 0 0 16px; }
.co__note { margin: 0; font-size: 12px; line-height: 1.4; color: var(--text-dim); }

/* Значок услуги в строке заказа — запасной путь для корзин, собранных до
   того, как у услуги появилась картинка (branding.py, поле image): у таких
   позиций в localStorage поля image нет, а перечёркнутая «фотографии нет»
   читалась бы как поломка. */
.icon--service { width: 22px; height: 22px; color: var(--accent); }

/* Название товара и пометки — одна строка. Раньше плашки стояли под
   заголовком отдельным рядом и читались как подпись к нему; на деле «хит» —
   часть того, как товар называют, и стоять она должна там же. По базовой
   линии, с переносом: у длинного названия плашки уезжают на следующую строку,
   а не ужимают заголовок. */
.pdp__name { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; }
.pdp__name h1 { margin: 0; }

/* Цена, старая цена и скидка — одна группа.
   
   Скидка есть у семи товаров из десяти, поэтому красить саму цену в красный
   бессмысленно: «распродажным» стал бы весь каталог. Работу делят между собой
   приглушённая зачёркнутая цена вплотную к нынешней и маленький чип с
   процентом — его хватает, чтобы выгоду заметили при беглом просмотре. */
.card__prices, .pdp__prices {
  display: flex; align-items: baseline; gap: 7px;
  min-width: 0;
}
/* Цена со старой ценой не разрывается никогда: перенос между ними и превращал
   их в два несвязанных числа. Если строка не помещается, вниз уезжает счётчик
   вариантов — он вторичен, а варианты и так видно по чипам выше. */
.card__prices { flex-wrap: nowrap; }
.card__foot { flex-wrap: wrap; }

/* На телефоне плитка вдвое уже. Прятать старую цену там нельзя — по отзывам
   без неё скидка на карточке «не считывалась», плашки с процентом людям
   мало. Поэтому на узком экране цены разрешено переносить: старая, помельче,
   стоит рядом, а если не влезла — второй строкой под нынешней. */
@media (max-width: 620px) {
  .card__prices { flex-wrap: wrap; row-gap: 0; column-gap: 6px; }
  .card__price-old { font-size: 11.5px; }
}

.card__price-old, .pdp__price-old, .pdp__buy-old {
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
.card__price-old { font-size: 12.5px; }
.pdp__price-old { font-size: 15px; }
.pdp__buy-old { font-size: 12.5px; }

/* В карточке товара скидка стоит рядом с ценой: там она одна и места хватает.
   В сетке — плашкой на фотографии (.badge--sale), см. _macros.html. */
.sale {
  padding: 1px 6px; border-radius: 6px;
  background: var(--accent-soft); color: var(--accent);
  font-size: 11.5px; font-weight: 650; letter-spacing: -.01em;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.sale--lg { font-size: 13px; padding: 2px 8px; }

/* Плашка скидки на фотографии — единственная цветная среди тёмных: остаток и
   пометки бэкофиса не должны спорить с ней за внимание. */
.badge--sale {
  background: var(--accent); color: var(--accent-ink, #fff);
  font-variant-numeric: tabular-nums;
}
.badge--mark { background: rgba(20, 20, 26, .74); color: #fff; }

/* Липкая панель покупки живёт только на узком экране — граница та же, что
   у .pdp__buy-price в базовых стилях (880px), иначе на планшете осталась бы
   висеть пустая обёртка. */
.pdp__buy-prices { display: none; }
@media (max-width: 880px) {
  .pdp__buy-prices { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
}

/* Сетка размеров на странице товара: одинаковые кнопки, как на обувных
   витринах. Крупно европейский размер, мелко сантиметры. Раскупленный размер
   остаётся в сетке приглушённо-серым — пропавшая клетка выглядела бы ошибкой
   витрины, а не «размер закончился». Перечёркивание не рисуем: серого хватает,
   а зачёркнутые цифры читаются хуже. */
.pdp__size-current { margin-left: 10px; font-size: 13.5px; font-weight: 550; color: var(--accent); }

.sizes { display: grid; grid-template-columns: repeat(auto-fill, minmax(64px, 1fr)); gap: 8px; }
.size-option {
  min-height: 50px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
  padding: 6px 4px;
  background: var(--surface); color: var(--text);
  border: 1.5px solid var(--border); border-radius: 11px;
  text-align: center; text-decoration: none;
  transition: border-color .14s, background .14s;
}
a.size-option { color: inherit; cursor: pointer; }
a.size-option:hover { border-color: var(--border-strong); }
.size-option.is-active { border-color: var(--accent); background: var(--accent-soft); }
.size-option__main { font-size: 14.5px; font-weight: 600; line-height: 1.2; }
.size-option__sub { font-size: 11px; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.size-option.is-active .size-option__sub { color: var(--accent); }
.size-option.is-out { background: var(--surface-2); border-color: transparent; color: var(--text-faint); }
.size-option.is-out .size-option__main { font-weight: 500; }

.sizes__hint { margin: 8px 0 0; font-size: 13px; font-weight: 550; color: var(--accent); }
/* встряска сетки, когда «В корзину» жмут без выбранного размера */
.sizes.is-nudged { animation: sizes-nudge .35s ease; }
@keyframes sizes-nudge {
  0%, 100% { transform: translateX(0); }
  30% { transform: translateX(-5px); }
  60% { transform: translateX(4px); }
}

/* Те же размеры в шторке предпросмотра — компактной кнопкой по центру. */
.variant--size { min-width: 58px; align-items: center; text-align: center; }

/* Чип «в бэкофисе» — служебный, поэтому тише остальных тегов: без заливки,
   пунктирная рамка отличает его от покупательских. Видит его только вошедший
   администратор (рисует staffChip в shop.js). */
.pdp__tag--staff {
  background: none;
  border: 1px dashed var(--border-strong);
  color: var(--text-faint);
  text-decoration: none;
}
.pdp__tag--staff:hover { color: var(--accent); border-color: var(--accent); }

/* ---------- остаток словами ----------
   Точного количества на витрине нет (shop/fmt.py): вместо «22 шт.» — «много»,
   «мало» или «последний». Прежняя витрина под /viewer/ считает по-старому и
   стилями в assets/styles.css пользуется той же, поэтому правки здесь, а не
   там: общий файл не должен знать, что у магазина остаток стал словом. */

/* Плашки «много / мало» на карточке в сетке больше нет — там стоит «В наличии»
   (см. «плашка наличия» ниже), а ступень остатка живёт на странице товара и в
   прайсе. Стили .badge--stock в assets/styles.css остаются прежней витрине. */

/* Колонка прайса. Числа в ней больше нет: «шт.» дорисовывать не к чему,
   равнять по правому краю тоже — слова разной длины так рвутся. */
.pl-table .pl-stock { text-align: left; }
td.pl-stock::after { content: none; }
td.pl-stock--few { color: #b45309; }
td.pl-stock--last { color: #be123c; font-weight: 600; }

/* ---------- корзина без прерывания ----------

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

   Взамен две вещи, и ни одна ничего не перекрывает: кнопка на месте становится
   счётчиком с кнопкой «Оформить» рядом, а число у корзины в шапке коротко
   подмигивает. Третьей был тост — всплывающая полоска «Добавлено» со ссылками
   в корзину и на оформление. Его убрали: покупатели жаловались, что полоска
   мешает — на телефоне она вставала над липкой полосой покупки и вместе с
   уведомлением о куках занимала треть экрана. Всё, что она говорила, теперь
   говорит сама строка покупки. Шторка по-прежнему открывается только по
   значку корзины (и по «Открыть корзину» под счётчиком) — то есть когда
   человек об этом попросил. */

:root {
  /* Сколько низа экрана занято чужой полосой. Выставляет cookies.js, снимает
     он же. Ноль здесь обязателен: без скриптов и на закрытом уведомлении
     переменной никто не задаёт, а calc() с пустым значением ломает правило
     целиком. */
  --bottom-inset: 0px;
  /* Высота липкой полосы покупки на телефоне — её меряет shop.js
     (paintBuyBar). От неё отсчитывается кнопка «наверх» (.to-top), чтобы
     стоять над полосой, а не под ней. Ноль обязателен по той же причине. */
  --buybar-h: 0px;
}

/* ---- счётчик на месте кнопки ---- */

/* Ступенчатый счётчик занимает ровно то место, где стояла кнопка: тот же
   порядок во флексе и та же высота, что у .btn--lg, — иначе строка покупки
   подпрыгивала бы на каждое первое добавление. */
.pdp__stepper {
  order: -1;
  min-height: 48px;
  border-color: var(--border-strong);
}
.pdp__stepper .stepper__btn { width: 44px; height: 46px; font-size: 19px; }
.pdp__stepper .stepper__value { padding: 0 14px; font-size: 14.5px; }

/* «Открыть корзину» под счётчиком — только на широком экране. На телефоне
   к корзине ведёт значок в шапке, а к оформлению — кнопка в самой липкой
   полосе; вторая дорога туда же была бы тем же самым дважды подряд. Это
   кнопка (корзина — шторка, а не адрес), поэтому у неё снят родной вид. */
.pdp__go {
  /* .pdp__info — колонка с gap: 18px, свой отступ кнопке не нужен. Без
     align-self растянутая на всю колонку кнопка ловила бы курсор далеко
     правее собственного текста. */
  align-self: flex-start;
  padding: 0; background: none; border: 0;
  font: inherit; font-size: 14px; font-weight: 600; color: var(--accent);
  text-decoration: none; cursor: pointer;
}
.pdp__go:hover { text-decoration: underline; }

/* «Оформить» рядом со счётчиком — на обоих экранах. На широком встаёт в ряд
   за счётчиком (order, как у него), на телефоне — в конец липкой полосы. */
.pdp__buy-go { order: -1; white-space: nowrap; }

/* Подпись «В корзине · N» под ценой — только на липкой полосе. */
.pdp__buy-incart { display: none; }

@media (max-width: 880px) {
  .pdp__go { display: none; }

  .pdp__buy-go {
    order: 0; flex: none;
    padding: 11px 14px; font-size: 14px;
  }
  /* «Оформить заказ» на полосе 375 px не помещается рядом с ценой и
     счётчиком — остаётся «Оформить». */
  .pdp__buy-go-word { display: none; }

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

     Про safe-area. Когда полоса стоит на самом низу, ей нужен запас под
     скруглением экрана. Когда её приподняли — этот запас уже отработало
     уведомление, и второй раз он превращается в лишнюю пустую полосу под
     кнопкой. max() отдаёт ровно недостающее. */
  .pdp__buy {
    bottom: var(--bottom-inset);
    padding-bottom: calc(10px + max(0px, env(safe-area-inset-bottom, 0px) - var(--bottom-inset)));
  }

  /* На узкой полосе счётчик ужимается до цифры: слово «в корзине» говорит
     подпись под ценой, а места на строку всего 375 px. Слово при этом остаётся
     в разметке и уезжает за край экрана, а не выключается display: none, —
     иначе диктор объявлял бы изменение количества голым числом «2». */
  /* На липкой полосе порядок другой: сначала цена, потом счётчик, потом
     «Оформить». order: -1 нужен только широкому экрану, где счётчик стоит на
     месте кнопки — а у самой кнопки на телефоне порядок тоже сбрасывается
     в ноль (assets/styles.css). */
  .pdp__stepper { order: 0; margin-left: auto; min-height: 42px; }
  .pdp__stepper .stepper__btn { width: 38px; height: 40px; }
  .pdp__stepper .stepper__value { padding: 0 6px; }
  .pdp__stepper-word {
    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;
  }

  /* Пока в корзине пусто, под ценой стоит остаток; как только вариант там
     оказался — «В корзине · N». Две подписи разом полосу не влезут, да и
     сказали бы об одном и том же. */
  .pdp__buy.is-in-cart .pdp__buy-note { display: none; }
  .pdp__buy.is-in-cart .pdp__buy-incart { display: block; }
}

/* ---- число у корзины в шапке ---- */

/* Подмигивание, а не прыжок: владелец просил сдержанно. Восьмая доля секунды и
   пятая часть размера — глазом ловится, внимание не отнимает. */
.cart-btn__count.is-bumped { animation: count-bump .22s var(--ease); }
@keyframes count-bump {
  50% { transform: scale(1.2); }
}
@media (prefers-reduced-motion: reduce) {
  .cart-btn__count.is-bumped { animation: none; }
}

/* ---------- вход через провайдера (VK ID, Яндекс ID) ----------
   Кнопки — свои ссылки, без чужих виджетов, но по фирменным требованиям
   провайдеров: у VK ровно #0077FF с их логотипом, у Яндекса чёрная с красным
   значком (перекрашивать нельзя, скругление у Яндекса — из их вариантов,
   12px). Никаких рамок и теней — плоские кнопки в ряд, как остальной сайт. */
.oauth { margin: 0 0 20px; }
.oauth__row { display: flex; gap: 10px; flex-wrap: wrap; }
.oauth__btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  flex: 1 1 190px; min-height: 44px; padding: 0 16px;
  border-radius: 12px;
  font-size: 14px; font-weight: 600; white-space: nowrap; text-decoration: none;
}
.oauth__btn:hover { filter: brightness(1.12); }
.oauth__btn--vk { background: #07f; color: #fff; }
.oauth__btn--ya { background: #000; color: #fff; }
.oauth__icon { width: 24px; height: 24px; flex: none; }
.oauth__btn--ya .oauth__icon { width: 20px; height: 20px; }
.oauth__hint { margin: 10px 0 0; font-size: 12.5px; color: var(--text-faint); }
/* Заметка о неудавшемся входе — тем же голосом, что ошибка оформления. */
.oauth__note {
  margin: 0 0 12px; padding: 11px 14px;
  background: #fdeceb; border: 1px solid #f3c9c6; border-radius: 10px;
  color: #b4231c; font-size: 14px;
}
.oauth__note--page { max-width: 380px; margin-top: 16px; }

/* На форме входа кнопки стоят после пароля — разделитель «или» с линиями
   по бокам, чтобы два способа входа не сливались в один столбец. */
.oauth--auth { margin: 0; }
.oauth__or {
  display: flex; align-items: center; gap: 12px;
  margin: 16px 0 12px; color: var(--text-faint); font-size: 12.5px;
}
.oauth__or::before, .oauth__or::after {
  content: ""; flex: 1; height: 1px; background: var(--border);
}

/* Кабинет: привязка провайдеров к своему аккаунту. */
.oauth--linked { margin: 18px 0 6px; }
.oauth--linked .oauth__btn { flex: 0 1 auto; min-height: 40px; font-size: 13.5px; }
.oauth__done {
  display: inline-flex; align-items: center;
  min-height: 40px; font-size: 13.5px; color: var(--text-dim);
}
.oauth__done .icon { width: 18px; height: 18px; color: var(--accent); }

/* ---------- «Главная» в шторке каталога ---------- */

/* На телефоне логотипа в шапке нет (поиск важнее, и вдвоём на 375 px они не
   помещаются), поэтому с главной уйти можно было, а вернуться — нет: остальные
   ссылки шапки ведут в кабинет и корзину. Пункт стоит первым в шторке и только
   в ней: на широком экране логотип на месте, и второй путь домой был бы лишним.
   Домик отличает его от разделов — у тех на этом месте число товаров. */
.cat--home { margin-bottom: 8px; }
.cat--home .icon { width: 18px; height: 18px; color: var(--text-faint); }
.cat--home.is-active .icon { color: inherit; }
@media (min-width: 901px) { .cat--home { display: none; } }

/* ---------- соцсети в подвале ---------- */

/* Ряд значков под адресом — те же каналы, что на doublett.ru. Значки
   монохромные, цветом текста: фирменные заливки соцсетей спорили бы с
   палитрой темы. Площадь нажатия 40 px — в подвал на телефоне попадают
   большим пальцем. Отрицательный отступ слева ставит первый значок в линию
   с логотипом и адресом, а не с его собственной подложкой. */
.social { display: flex; gap: 2px; margin: 12px 0 0 -9px; padding: 0; list-style: none; }
.social__link {
  display: grid; place-items: center;
  width: 40px; height: 40px; border-radius: 10px;
  color: var(--text-dim);
  transition: background .15s, color .15s;
}
.social__link:hover { background: var(--surface-2); color: var(--accent); }
.social__link .icon { width: 22px; height: 22px; }

/* ---------- касания на телефоне ---------- */

/* Двойное касание в Safari увеличивает страницу, и по кнопкам «+» и «−»
   попадали ровно так. manipulation оставляет прокрутку и щипок (доступность
   не страдает), но отбирает у двойного тапа особый смысл — и вместе с ним
   задержку в 300 мс на одиночном. Перечень элементов нужен из-за своих
   прокручиваемых областей (шторки, ленты): цепочка touch-action обрывается
   на ближайшем контейнере прокрутки, и правило на html туда не достаёт. */
html, body, a, button, input, select, textarea, label, summary { touch-action: manipulation; }

/* iOS Safari при фокусе в поле мельче 16 px наезжает на него зумом, и после
   отправки формы страница остаётся увеличенной. Ровно 16 px — там, где в поля
   тыкают пальцем; на десктопе с мышью остаются прежние 15 px. Классы в списке
   перебивают .fld и .field: у них собственный размер с большей специфичностью,
   чем у голого input. Флажки и радио не трогаем — у них размер не про текст. */
@media (pointer: coarse) {
  input:not([type="checkbox"]):not([type="radio"]), select, textarea,
  .fld input, .fld textarea, .field input, .field textarea,
  .search input, .pl-search input, .select select { font-size: 16px; }
}

/* ---------- кнопка «наверх» ---------- */

/* Появляется, когда прокрутили полтора экрана (порог — в shop.js,
   wireBackToTop), и стоит над всем, что уже занимает низ: над липкой полосой
   покупки и над уведомлением о куках — обе сообщают свою высоту числом.
   Запас под скругление экрана добавляем только той частью, которую эти полосы
   ещё не отработали: иначе на iPhone под кнопкой копилась бы лишняя пустота.

   Скрыта через visibility, а не hidden: атрибут выключает display, и
   появление не анимируется. Без скриптов кнопка так и остаётся невидимой и
   ненажимаемой — показать её некому. */
.to-top {
  position: fixed; z-index: 25;
  right: var(--gutter);
  bottom: calc(var(--bottom-inset) + var(--buybar-h) + 16px
               + max(0px, env(safe-area-inset-bottom, 0px) - var(--bottom-inset) - var(--buybar-h)));
  width: 44px; height: 44px; padding: 0;
  display: grid; place-items: center;
  background: var(--surface);
  border: 1px solid var(--border-strong); border-radius: 50%;
  box-shadow: var(--shadow-md);
  color: var(--text-dim); cursor: pointer;
  opacity: 0; transform: translateY(8px); visibility: hidden;
  transition: opacity .2s var(--ease), transform .2s var(--ease), visibility 0s .2s;
}
.to-top.is-visible { opacity: 1; transform: none; visibility: visible; transition-delay: 0s; }
.to-top:hover { color: var(--text); border-color: var(--text-faint); }
.to-top .icon { width: 20px; height: 20px; }
@media (prefers-reduced-motion: reduce) { .to-top { transition: none; } }

/* ---------- полоса брендов на телефоне ----------
   На широком экране полоса — плитки с логотипами, и все двадцать марок видны
   разом. На узком та же плитка складывалась в четыре ряда и съедала первый
   экран. Поэтому до 900 px (там же, где каталог уходит в шторку) полоса
   становится одной строкой чипов с прокруткой, как у мобильных магазинов:
   имя и голое число, без логотипа, без заголовка и без «сбросить» — его роль
   у чипа «Все». Разметка та же самая, меняются только стили: ссылки остаются
   ссылками, тесты и диктор видят прежний текст. Активный чип в поле зрения
   подводит wireBrandbar в shop.js. */
@media (max-width: 900px) {
  .brandbar { padding: 0; margin-bottom: 14px; background: none; border: 0; border-radius: 0; }
  .brandbar__head { display: none; }
  /* Отрицательные поля выводят строку из отступа .shell: чипы уезжают за край
     экрана, а не обрываются на нём — так видно, что строку можно листать. */
  .brandbar__list {
    display: flex; gap: 6px;
    overflow-x: auto; overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
    margin: 0 calc(-1 * var(--gutter)); padding: 2px var(--gutter) 6px;
    /* Точка привязки считается от края прокрутки, а не от внутреннего
       отступа: без этого первый чип прилипал бы к краю экрана. */
    scroll-padding-inline: var(--gutter);
    scrollbar-width: none;
  }
  .brandbar__list::-webkit-scrollbar { display: none; }
  .brandbar .brand-chip {
    flex: none; flex-direction: row; align-items: baseline; gap: 5px;
    padding: 7px 12px;
    background: var(--surface-2);
    border-width: 1px; border-color: transparent; border-radius: 100px;
    scroll-snap-align: start;
  }
  .brandbar .brand-chip:hover { border-color: transparent; background: var(--border); }
  .brandbar .brand-chip.is-active { border-color: var(--accent); background: var(--accent-soft); }
  .brandbar .brand-mark,
  .brandbar .brand-chip__tail,
  .brandbar .brand-chip__unit { display: none; }
  .brandbar .brand-chip__name { font-size: 13px; }
  .brandbar .brand-chip__count { font-size: 11px; }
}

/* ---------- панель раздела на телефоне ----------
   Заголовок занимает всю строку, а сортировка уходит из панели в строку
   фильтров — первой пилюлей перед чипами брендов (см. «сортировка на
   телефоне» в конце файла). Раньше они делили одну строку, и длинный
   заголовок («Butterfly — накладки») выталкивал селект второй строкой к
   правому краю: получалась кривая лесенка над каждым разделом. */
@media (max-width: 620px) {
  .toolbar--catalog .toolbar__right { width: auto; margin: 0; }
}

/* ---------- плашка наличия ----------
   На карточке в сетке вместо «много / мало / последний» — «В наличии»:
   покупателю в сетке нужен ответ «есть ли», а ступень остатка он видит на
   странице товара, где выбирает вариант. Плашка тёмная, как остальные
   служебные, — на светлых и тёмных фото читается одинаково; точка зелёная.
   «Нет в наличии» — полупрозрачнее и с серой точкой, чтобы не спорить со
   скидкой за внимание. */
/* Светлая, а не тёмная, как у скидки: «в наличии» стоит на каждой карточке,
   и тёмная плашка на каждой превращала сетку в россыпь чёрных пилюль.
   Зелёная точка говорит всё сама, текст — приглушённый. */
.badge--avail {
  display: inline-flex; align-items: center; gap: 5px; white-space: nowrap;
  background: rgba(255, 255, 255, .88); color: var(--text-dim);
  border: 1px solid rgba(20, 20, 26, .06);
}
.badge--avail::before {
  content: ""; width: 6px; height: 6px; flex: none;
  border-radius: 50%; background: #22c55e;
}
.badge--avail-none { color: var(--text-faint); }
.badge--avail-none::before { background: var(--text-faint); }
/* Распроданный товар в выдаче стоит последним и приглушён: карточка
   остаётся ради тех, кто ищет именно эту модель, но не спорит с тем, что
   можно купить. Приглушается только фото — название и цену читать должно
   быть так же легко. Второй селектор — ради плавного появления фото
   (`.is-ready`), которое иначе вернуло бы ему полную яркость. */
.card--out .card__media img,
html.js .card--out .card__media.is-ready img { opacity: .55; }

/* ---------- лайтбокс: листание ----------
   Сам лайтбокс (#lightbox, base.html) — одна картинка на тёмном фоне, стили
   в assets/styles.css. Стрелки и счётчик дорисовывает shop.js (lightboxNav),
   когда галерея передала ему несколько фотографий; при одной они скрыты.
   Листается ещё клавишами ← → и свайпом — этому стили не нужны. */
.lightbox__nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 44px; height: 44px;
  display: grid; place-items: center;
  background: rgba(255, 255, 255, .12); color: #fff;
  border: 0; border-radius: 50%;
  cursor: pointer;
  transition: background .15s, opacity .15s;
}
.lightbox__nav svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.lightbox__nav:hover { background: rgba(255, 255, 255, .22); }
.lightbox__nav:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.lightbox__nav[disabled] { opacity: .3; cursor: default; }
.lightbox__nav--prev { left: 16px; }
.lightbox__nav--next { right: 16px; }
.lightbox__counter {
  position: absolute; bottom: 18px; left: 50%; transform: translateX(-50%);
  padding: 4px 12px; border-radius: 100px;
  background: rgba(255, 255, 255, .12); color: #fff;
  font-size: 12.5px; font-variant-numeric: tabular-nums;
}
/* На телефоне стрелки заняли бы края и без того тесного экрана — там свайп.
   Крестик и счётчик остаются. */
@media (max-width: 700px) {
  .lightbox__nav { display: none; }
}

/* ---------- превью: «Оформить» рядом со счётчиком ----------
   Тоста больше нет, и что вещь легла в корзину, показывает сама строка
   покупки: счётчик плюс кнопка дальше — как на странице товара. */
.pv__go {
  display: inline-flex; align-items: center; justify-content: center;
  flex: 1; height: 40px; padding: 0 16px;
  font-size: 14px; white-space: nowrap;
}

/* ================= кабинет: разделы, заказы с превью, данные =================
   Три раздела — «Заказы», «Данные», «Настройки». Сервер отдаёт все три подряд,
   каждый со своим заголовком: без скрипта это просто длинная страница.
   Скрипт (wireAccountTabs) вешает на контейнер .account--tabs, и тогда
   навигация становится вкладками, а заголовки разделов прячутся — их роль
   берёт на себя выбранная вкладка. */
.account__tabs {
  display: flex; gap: 2px;
  margin: 4px 0 24px; padding: 3px;
  border-radius: 12px; background: var(--surface-2);
}
.account__tab {
  flex: 1 1 0; padding: 8px 14px;
  border-radius: 9px; text-align: center;
  font-size: 14px; font-weight: 600; color: var(--text-dim);
  text-decoration: none; white-space: nowrap;
  transition: background .15s var(--ease), color .15s var(--ease);
}
.account__tab:hover { color: var(--text); }
.account__tab.is-active { background: var(--surface); color: var(--text); box-shadow: var(--shadow-sm); }

.account__section + .account__section { margin-top: 36px; }
.account--tabs .account__section + .account__section { margin-top: 0; }
.account__heading { margin: 0 0 14px; font-size: 20px; letter-spacing: -.02em; }
.account--tabs .account__heading {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}
.account__subheading { margin: 28px 0 12px; font-size: 16px; letter-spacing: -.01em; }
.account__section .orders { margin-top: 0; }

/* Формы данных: те же .field, что на входе; ширина как у формы входа. */
.account__form { max-width: 420px; }
.account__form .btn { margin-top: 4px; }
.account__static { display: block; font-size: 15px; }
.account__forget { max-width: 420px; margin: 10px 0 0; }
.account__saved {
  display: inline-flex; align-items: center;
  margin: 0 0 14px; font-size: 14px; color: #0f7b3f;
}
.account__saved .icon { color: #0f7b3f; }
.account__section .checkout__error { max-width: 420px; margin-bottom: 14px; }

/* Строки заказа с превью. Квадрат под картинку есть у каждой строки, в том
   числе у удалённого товара и у услуги, — иначе названия одного заказа
   прыгали бы по отступу. У удалённого в квадрате «картинки нет», у услуги
   — значок ракетки. */
.order__lines--pics { gap: 10px; }
.order__line--pic { align-items: center; gap: 12px; color: var(--text); }
.order__thumb {
  flex: none; width: 52px; height: 52px;
  border-radius: 10px; object-fit: cover; background: var(--surface-2);
}
.order__thumb--none {
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--text-faint);
}
.order__thumb--none .icon { width: 22px; height: 22px; }
.order__title { flex: 1 1 auto; min-width: 0; font-size: 14px; }
.order__link { color: inherit; text-decoration: none; }
.order__link:hover { color: var(--accent); }
.order__qty { color: var(--text-faint); white-space: nowrap; }
/* Сборка ракетки в кабинете: по строке на основание под названием услуги. */
.order__build { display: block; font-size: 12.5px; color: var(--text-dim); }
.order__sum { flex: none; font-variant-numeric: tabular-nums; }

/* ---------- свёрнутая панель заказа на телефоне ----------
   На ≤940 px панель «Ваш заказ» стоит над формой одной строкой: «Ваш
   заказ · 2 товара · 8 370 ₽» и шеврон. Нажатие раскрывает тело панели:
   состав со счётчиками, услуги, доставка, итог. Классы ставит wireCheckout
   (shop.js): is-collapsible — «панель умеет сворачиваться», is-collapsed —
   «сейчас свёрнута»; открывается она по желанию и по подсказке сборки
   (expandSummary).

   Кнопка-заголовок в разметке всегда, но видна только здесь: на широком
   экране панель липкая справа и сворачивать её незачем, а без скрипта
   раскрывать было бы некому. */
.co__sum-toggle {
  display: none; width: 100%; align-items: center; gap: 10px;
  padding: 14px 16px; border: 0; border-radius: var(--radius);
  background: none;
  font: inherit; font-size: 14.5px; font-weight: 650; letter-spacing: -.01em;
  color: var(--text); text-align: left; cursor: pointer;
}
.co__sum-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.co__sum-toggle-text { flex: 1; min-width: 0; }
.co__sum-brief { font-weight: 500; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.co__sum-chevron {
  width: 18px; height: 18px; flex: none; color: var(--text-faint);
  transition: transform .18s var(--ease);
}
@media (max-width: 940px) {
  html.js .co__side.is-collapsible .co__summary { padding: 0; }
  html.js .co__side.is-collapsible .co__sum-toggle { display: flex; }
  html.js .co__side.is-collapsible .co__sum-body { padding: 0 16px 16px; }
  html.js .co__side.is-collapsible.is-collapsed .co__sum-body { display: none; }
  html.js .co__side.is-collapsible:not(.is-collapsed) .co__sum-chevron { transform: rotate(180deg); }
  /* Заголовок панели уже написан на кнопке — второй раз в теле он не нужен. */
  html.js .co__side.is-collapsible .co__sum-head { display: none; }
}
@media (prefers-reduced-motion: reduce) { .co__sum-chevron { transition: none; } }

/* ---------- сортировка на телефоне: пилюля в строке брендов ----------
   На ≤620 px заголовок раздела занимает всю строку, а сортировка встаёт в
   строку фильтров первой пилюлей — перед чипами брендов, в том же ряду и того
   же роста. Где полосы брендов нет (поиск, страница бренда, подборка), пилюля
   стоит в этой строке одна.

   Пилюля — это тот же <select>, только прозрачный: он лежит поверх
   нарисованной пилюли (.select__pill со значком и короткой подписью, её
   пишет сервер в sort_select) и ловит нажатие, так что открывается обычный
   системный список — на телефоне он удобнее любого своего. Форма та же
   (data-autosubmit), без скрипта рядом появляется кнопка «ОК».

   Строка «сортировка + бренды» собирается без переноса узлов: панель
   заголовка и секция брендов растворяются (display: contents), а их дети
   становятся элементами <main> с flex-wrap: заголовок на всю строку, пилюля и
   строка чипов — рядом, всё остальное — по строке на каждого (порядок задаёт
   order). Поэтому у этих двух обёрток на телефоне нет координат, и якоря
   переходов ведут на заголовок и на саму строку чипов (catalog.html). */
.select__pill { display: none; }
@media (max-width: 620px) {
  .main--catalog { display: flex; flex-wrap: wrap; align-items: center; gap: 14px 8px; }
  .main--catalog > .toolbar--catalog,
  .main--catalog > .brandbar { display: contents; }
  .main--catalog .toolbar__title { flex: 0 0 100%; }
  .main--catalog .toolbar__right { flex: none; order: 1; }
  /* min-width: 0 обязателен: строки подразделов внутри не переносятся
     (nowrap), и без него ряд «Других разделов» раздвигал бы страницу до
     своей полной ширины — flex-элемент не ужимается ниже min-content. */
  .main--catalog > .subcats,
  .main--catalog > .grid,
  .main--catalog > .empty,
  .main--catalog > .cat-strip--siblings { flex: 0 0 100%; min-width: 0; order: 2; }
  /* Зазоры теперь у <main>: собственные поля строк сложились бы с ними. */
  .main--catalog > .subcats { margin-bottom: 0; }
  .main--catalog > .cat-strip--siblings { margin-top: 18px; }
  /* Строка чипов — справа от пилюли, до правого края экрана: левый отступ
     и левое выведение за край ей больше не нужны, их даёт пилюля. */
  .main--catalog .brandbar__list {
    flex: 1 1 0; min-width: 0; order: 1;
    margin: 0 calc(-1 * var(--gutter)) 0 0;
    padding: 2px var(--gutter) 2px 0;
    scroll-padding-inline: 0 var(--gutter);
  }

  /* Сортировка и «Фильтры» переезжают внутрь строки чипов (wireBrandbar,
     shop.js), и вся строка листается целиком — так бренды не упираются в
     неподвижные пилюли. Строка тогда снова тянется от края до края. */
  .main--catalog .brandbar__list.is-joined {
    flex: 0 0 100%;
    margin: 0 calc(-1 * var(--gutter));
    padding: 2px var(--gutter);
    scroll-padding-inline: var(--gutter);
  }
  /* order: -1 перебивает order: 1 панели заголовка — внутри строки пилюли
     стоят первыми, перед чипами. */
  /* Своя точка привязки обязательна: без неё scroll-snap на загрузке
     подтягивал строку к первому чипу, и пилюли оказывались за левым краем. */
  .brandbar__list .toolbar__right {
    flex: none; display: flex; gap: 6px; order: -1; scroll-snap-align: start;
    /* Перебиваем width: 100% и space-between панели на ≤620 (assets/styles.css):
       внутри строки пилюли стоят вплотную, а не по краям экрана. */
    width: auto; justify-content: flex-start;
  }

  /* .main--catalog, а не .toolbar--catalog: на телефоне узел с сортировкой
     переезжает в строку чипов (joinFilterRow), и правила обязаны найти его
     и там. */
  .main--catalog .select { position: relative; display: inline-flex; gap: 6px; }
  .main--catalog .select__pill {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 7px 12px 7px 10px;
    background: var(--surface-2);
    border: 1px solid transparent; border-radius: 100px;
    font-size: 13px; font-weight: 600; line-height: 1.2; white-space: nowrap;
    color: var(--text);
  }
  .main--catalog .select__pill .icon { width: 15px; height: 15px; color: var(--text-dim); }
  .main--catalog .select select {
    position: absolute; inset: 0; z-index: 1;
    width: 100%; height: 100%; padding: 0;
    border: 0; background: none;
    opacity: 0; cursor: pointer;
  }
  .main--catalog .select select:focus-visible + .select__pill { outline: 2px solid var(--accent); outline-offset: 2px; }
  /* Кнопка «ОК» без скрипта стоит за пилюлей, а не под прозрачным селектом. */
  .main--catalog .select noscript .btn { position: relative; z-index: 2; height: 32px; padding: 0 12px; }
}

/* ================= фильтры каталога =================
   Кнопка «Фильтры» рядом с сортировкой и шторка с формой (catalog.html).
   Форма — обычный GET на адрес раздела (shop/routes.py, «фильтры каталога»):
   без скрипта кнопка — ссылка на #filters, и шторку открывает :target, как
   боковой каталог; закрывают ссылки на «#» (крестик и подложка). Со скриптом
   клик перехватывается (wireFilters в shop.js), шторка живёт на классе
   is-open, а число на кнопке «Показать N товаров» спрашивает сервер.

   На широком экране шторка — панель справа, как корзина; до 700 px — лист
   снизу на 85 % высоты, с ручкой, свайп по шапке вниз закрывает. Разметка
   одна, разница только в стилях. Токены те же, что у .sheet
   (viewer/styles.css), но без вложенных рамок: группы разделены линиями,
   а не карточками в карточке. */

.filters-btn {
  display: inline-flex; align-items: center; gap: 7px;
  height: 36px; padding: 0 14px;
  background: var(--surface); color: var(--text);
  border-radius: var(--radius-sm);
  box-shadow: inset 0 0 0 1px var(--border-strong);
  font-size: 13.5px; font-weight: 600; text-decoration: none;
  white-space: nowrap;
}
.filters-btn:hover { background: var(--surface-2); color: var(--text); }
.filters-btn .icon { width: 16px; height: 16px; color: var(--text-dim); }
/* Число отмеченных фильтров: цветом, как счётчик корзины, — это единственное
   на кнопке, что меняется, и ему надо быть заметным. */
.filters-btn__count {
  min-width: 18px; height: 18px; padding: 0 5px;
  display: inline-grid; place-items: center;
  background: var(--accent); color: var(--accent-ink);
  border-radius: 100px; font-size: 11px; line-height: 1;
  font-variant-numeric: tabular-nums;
}

/* На телефоне кнопка — той же пилюлей, что и сортировка (см. «сортировка на
   телефоне»), первой в строке чипов брендов, того же роста (32 px, как
   пилюля с её полями и кеглем). Слова «Фильтры» на ней нет: вместе с
   подписью сортировки оно съедало строку целиком, и полосе брендов на
   375 px оставалось 75 px — один чип. Остаётся значок, которым фильтры
   обозначают все мобильные магазины, и счётчик, когда что-то отмечено;
   диктору слово читается из aria-label. */
@media (max-width: 620px) {
  .toolbar--catalog .toolbar__right { gap: 6px; }
  .filters-btn {
    height: 32px; padding: 0 10px; gap: 5px;
    background: var(--surface-2); box-shadow: none;
    border: 1px solid transparent; border-radius: 100px;
    font-size: 13px; line-height: 1.2;
  }
  .filters-btn__text { display: none; }
  .filters-btn .icon { width: 16px; height: 16px; color: var(--text); }
  .filters-btn__count { min-width: 16px; height: 16px; font-size: 10.5px; }
}

/* ---------- шторка ---------- */
.fsheet { position: fixed; inset: 0; z-index: 50; display: none; }
.fsheet.is-open, .fsheet:target { display: block; }
.fsheet__scrim {
  position: absolute; inset: 0;
  background: rgba(10, 10, 14, .5);
  animation: fade .2s var(--ease);
}
.fsheet__panel {
  position: absolute; inset: 0 0 0 auto;
  width: min(420px, 100%); margin: 0;
  display: flex; flex-direction: column;
  background: var(--surface);
  box-shadow: var(--shadow-lg);
  animation: slide-in .28s var(--ease);
}
.fsheet__head {
  position: relative; flex: none;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 14px 12px 12px 22px;
  border-bottom: 1px solid var(--border);
}
.fsheet__title { font-size: 19px; letter-spacing: -.02em; }
.fsheet__grip { display: none; }
.fsheet__close { color: var(--text-dim); }
.fsheet__close:hover { background: var(--surface-2); color: var(--text); }
.fsheet__body {
  flex: 1; min-height: 0;
  overflow-y: auto; overscroll-behavior: contain;
  padding: 0 22px;
}
.fsheet__foot {
  flex: none; display: flex; gap: 10px;
  padding: 12px 22px calc(12px + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--border);
  background: var(--surface);
}
.fsheet__foot .btn { display: inline-flex; align-items: center; justify-content: center; }
.fsheet__reset { flex: none; }
.fsheet__apply { flex: 1; }
.fsheet__apply:disabled { opacity: .5; cursor: default; filter: none; }

/* Группы — без рамок, между ними линия. Заголовок группы мелкий и полужирный,
   как у секций сайдбара. */
.fgroup { min-width: 0; margin: 0; padding: 16px 0; border: 0; border-bottom: 1px solid var(--border); }
.fgroup:last-child { border-bottom: 0; }
/* float — не украшение: <legend> браузер сажает на линию рамки fieldset,
   ВЫШЕ его внутреннего отступа, и «Цена» прилипала к шапке шторки, хотя
   padding-top у группы есть. Плавающий legend становится обычным блоком и
   отступ наконец оказывается над ним. */
.fgroup__title { float: left; width: 100%; padding: 0; margin-bottom: 10px; font-size: 13px; font-weight: 600; }
/* clear обязателен: flex-контейнер полей цены сам по себе не заходит под
   float и, не найдя места рядом с ним, ужимался в нулевую ширину. */
.fgroup > :not(legend) { clear: both; }
.fgroup::after { content: ""; display: block; clear: both; }

/* Цена: два поля «от / до». Само поле без рамки, рамка у обёртки, чтобы
   подпись «от» стояла внутри. */
.fprice { display: flex; gap: 8px; }
.fprice__field {
  flex: 1; min-width: 0;
  display: flex; align-items: center; gap: 8px;
  height: 40px; padding: 0 12px;
  background: var(--bg);
  border: 1.5px solid var(--border); border-radius: 10px;
  color: var(--text-faint); font-size: 13px;
  transition: border-color .13s, background .13s;
}
.fprice__field:hover { border-color: var(--border-strong); }
.fprice__field:focus-within { background: var(--surface); border-color: var(--accent); }
.fprice__field input {
  flex: 1; min-width: 0; width: 100%; padding: 0;
  border: 0; background: none; outline: none;
  font-size: 15px; color: var(--text);
  -moz-appearance: textfield; appearance: textfield;
}
.fprice__field input::-webkit-outer-spin-button,
.fprice__field input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.fprice__field input::placeholder { color: var(--text-faint); }
/* 16 px на телефоне — иначе iOS приближает страницу при фокусе */
@media (max-width: 560px) { .fprice__field input { font-size: 16px; } }

/* Галочки: бренды и «только в наличии». Настоящий чекбокс, а не чип, — список
   до двадцати строк читается столбиком, и галочка в нём привычнее. */
.fgroup__list { display: flex; flex-direction: column; }
.fcheck {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 0; cursor: pointer; font-size: 14px;
}
.fcheck input {
  flex: none; width: 18px; height: 18px; margin: 0;
  accent-color: var(--accent); cursor: pointer;
}
.fcheck__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fcheck__count { flex: none; font-size: 12.5px; color: var(--text-faint); font-variant-numeric: tabular-nums; }
/* Фильтр, который дал бы пустую выдачу: гасим, но оставляем на месте — так
   видно, что марка в разделе есть, просто не при этих галочках. */
.fcheck.is-dead { opacity: .45; cursor: default; }

/* Атрибуты — чипами, как категории в фильтре прайса (.pl-cat): сам чекбокс
   спрятан, но остаётся ради клавиатуры и диктора. Выбор меняет только цвета:
   жирность меняла бы ширину, и вся строка перекладывалась бы на каждый клик. */
.fgroup__chips { display: flex; flex-wrap: wrap; gap: 7px; }
.fchip {
  position: relative;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px;
  background: var(--surface-2);
  border: 1px solid transparent; border-radius: 100px;
  font-size: 13px; color: var(--text-dim);
  cursor: pointer; user-select: none; -webkit-user-select: none;
  transition: background .13s, color .13s, border-color .13s;
}
.fchip input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; pointer-events: none; }
.fchip:hover { border-color: var(--border-strong); color: var(--text); }
.fchip:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.fchip:has(input:checked) { background: var(--accent-soft); border-color: var(--accent); color: var(--text); }
.fchip__count { font-size: 11.5px; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.fchip:has(input:checked) .fchip__count { color: inherit; opacity: .72; }
.fchip.is-dead { opacity: .4; cursor: default; }
.fchip.is-dead:hover { border-color: transparent; color: var(--text-dim); }

/* ---------- применённые фильтры над сеткой ---------- */
.fchips { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 18px; }
.fchips__chip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 5px 8px 5px 12px;
  background: var(--accent-soft); color: var(--text);
  border: 1px solid var(--accent); border-radius: 100px;
  font-size: 13px; line-height: 1.2; text-decoration: none;
  transition: background .13s, color .13s;
}
.fchips__chip .icon { width: 14px; height: 14px; color: var(--accent); }
.fchips__chip:hover { background: var(--accent); color: var(--accent-ink); }
.fchips__chip:hover .icon { color: inherit; }
.fchips__reset {
  margin-left: 4px;
  font-size: 13px; font-weight: 600; color: var(--accent); text-decoration: none;
}
.fchips__reset:hover { text-decoration: underline; }

/* ---------- лист снизу на телефоне ---------- */
@media (max-width: 700px) {
  .fsheet__panel {
    inset: auto 0 0 0; width: 100%; height: 85dvh;
    border-radius: 18px 18px 0 0;
    animation: slide-up .28s var(--ease);
  }
  .fsheet__head { padding-top: 20px; }
  .fsheet__grip {
    display: block; position: absolute; top: 8px; left: 50%;
    width: 36px; height: 4px; margin-left: -18px;
    background: var(--border-strong); border-radius: 2px;
  }
}
/* На странице раздела строки раскладывает <main> (см. «сортировка на
   телефоне»): чипы — своей строкой перед сеткой, зазор даёт сам <main>. */
@media (max-width: 620px) {
  .main--catalog > .fchips { flex: 0 0 100%; min-width: 0; order: 2; margin-bottom: 0; }
}
@media (prefers-reduced-motion: reduce) { .fsheet__panel, .fsheet__scrim { animation: none; } }

/* ---------- форма входа на телефоне ----------
   Карточка входа шириной 380 px стояла у левого края, и на широких телефонах
   (412–430 px) справа оставалась пустая полоса. По центру она читается как
   единственное, что есть на странице, — так и должно быть. */
@media (max-width: 620px) {
  .auth { margin-inline: auto; }
  .account__head { justify-content: center; text-align: center; }
}

/* ---------- снимки внутри текста ----------
   Правовые страницы и статьи блога — это готовый html, который владелец
   правит в постороннем редакторе. Оттуда приезжает разметка вида
   «<p><img><img><img></p>»: в редакторе это ряд, а на сайте глобальное
   `img { display: block }` (viewer/styles.css) ставило снимки столбиком, во
   всю ширину колонки. На контактах так вышло четыре экрана инструкции «как
   нас найти», а в статье про открытие — три фотографии по 960×1280, каждая
   выше окна.

   Чиним показом, а не разметкой. Разметку владелец правит руками, и первая же
   правка абзаца вернула бы столбик; правило же работает на любом тексте, в
   том числе на том, который загрузят завтра.

   `:has()` тут не новое требование к браузеру: на нём уже держится подсветка
   карточки с фокусом (viewer/styles.css, `.card:has(...)`). */
.desc p:has(img + img) {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px;
  align-items: start;
}
/* Кадр не режем: на этих снимках показывают калитку с кодовой панелью и дверь
   с логотипом — обрезанный край и есть то, что человек ищет во дворе. */
.desc p:has(img + img) img { width: 100%; height: auto; margin: 0; }

/* Одиночная картинка в тексте: потолок по высоте, пропорции свои. Тот же
   приём и то же число, что у обложки статьи (.blog-card__cover ниже). */
.legal__body img { max-height: 480px; }

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

   Теперь это сетка карточек. От `.grid` она отличается шириной колонки: у
   товара под фотографией две строки, у статьи — заголовок в три строки и
   описание, и в 250 px такая карточка не живёт. 300 px дают три колонки на
   широком экране, две на планшете и одну на телефоне. Потолок в три штуки —
   чтобы на большом мониторе карточки не расползались в узкие полоски. */
.blog-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr));
  gap: clamp(12px, 1.6vw, 22px);
}
@media (min-width: 1240px) { .blog-list { grid-template-columns: repeat(3, 1fr); } }

.blog-card {
  position: relative;
  display: flex; flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  transition: transform .18s var(--ease), box-shadow .18s var(--ease), border-color .18s;
}
.blog-card:hover { transform: translateY(-3px); border-color: var(--border-strong); box-shadow: var(--shadow-md); }
.blog-card:has(.blog-card__link:focus-visible) { border-color: var(--accent); }

/* Обложки владелец загружает какие есть: приезжают и квадратные 1270×1270, и
   широкие 1280×848. Рамка одна на всех, иначе ряд карточек стоит уступами;
   `cover` без потери смысла — обложка тут украшение, содержание в заголовке. */
.blog-card__cover {
  aspect-ratio: 16 / 10;
  background: var(--tile);
  overflow: hidden;
}
.blog-card__cover img { width: 100%; height: 100%; object-fit: cover; }

.blog-card__body { display: flex; flex-direction: column; gap: 6px; padding: 14px 16px 16px; }
.blog-card__date { font-size: 12.5px; color: var(--text-faint); }
.blog-card__title { margin: 0; font-size: 17px; font-weight: 700; letter-spacing: -.015em; line-height: 1.3; }
/* Ссылка растянута на всю карточку: попадать надо в карточку, а не в текст. */
.blog-card__link { color: inherit; text-decoration: none; }
.blog-card__link::after { content: ""; position: absolute; inset: 0; }
/* Описание обрезаем на четвёртой строке: у статей оно от одной строки до
   пяти, и без потолка карточки в ряду разной высоты. */
.blog-card__text {
  margin: 0;
  font-size: 13.5px; line-height: 1.5; color: var(--text-dim);
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 4; line-clamp: 4;
  overflow: hidden;
}

/* ---------- таблица в правовом тексте ----------
   `.desc table` (viewer/styles.css) стоит `display: block; overflow-x: auto`.
   Для описаний из МойСклада это верно: там встречаются таблицы характеристик
   в десяток колонок, и без прокрутки они рвут страницу. Но `display: block`
   снимает с таблицы табличную раскладку внешнего блока — ширину 100 %
   получает обёртка, а сама таблица внутри неё сжимается по содержимому. На
   странице про куки из-за этого колонки стояли вкривь и вкось.

   В правовом тексте таблица одна, колонок в ней три, и в колонку страницы она
   помещается целиком — в том числе на телефоне, где ячейки просто переносят
   строки. Поэтому здесь она обычная таблица. */
.legal__body table { display: table; width: 100%; }
.legal__body th { text-align: left; font-weight: 600; }
.legal__body td, .legal__body th {
  vertical-align: top;
  /* `.desc` рвёт слова где угодно (`overflow-wrap: anywhere`) — в узкой
     ячейке от этого получается лесенка из огрызков. Внутри таблицы переносим
     по-человечески, по словам. */
  overflow-wrap: normal;
}
/* Крайние колонки — короткие подписи, середина — объяснение. Без подсказки
   ширины браузер делит поровну, и «Метка сессии» переносится на три строки,
   пока в средней колонке остаётся место. */
.legal__body th:first-child, .legal__body td:first-child { width: 22%; }
.legal__body th:last-child, .legal__body td:last-child { width: 22%; }
/* На телефоне колонке страницы 343 px, и три колонки таблицы в них помещаются
   только за счёт переносов. Шрифт мельче и отступы теснее дают ячейкам ещё
   немного места; подсказки ширины здесь наоборот мешают — делить надо по
   содержимому, а не поровну. */
@media (max-width: 620px) {
  .legal__body table { font-size: 13px; }
  .legal__body td, .legal__body th { padding: 6px 7px; }
  .legal__body th:first-child, .legal__body td:first-child,
  .legal__body th:last-child, .legal__body td:last-child { width: auto; }
}

/* ---------- обратная связь (shop/feedback.py, _feedback.html) ----------
   Оценка заказа — тихий блок под карточкой заказа: без рамки в рамке, одна
   строка звёзд и поле, которое со скриптом раскрывается после оценки. На
   экранах «заказ принят» и «оплата прошла» блок стоит внутри итоговой
   карточки и отделяется чертой. */

.fb { margin: 22px 0; max-width: 560px; }
.co__done .fb {
  margin: 28px auto 0; padding-top: 22px;
  border-top: 1px solid var(--border); text-align: left;
}
.fb[hidden] { display: none; }
.fb__form { margin: 0; }
.fb__title { margin: 0 0 2px; font-size: 17px; font-weight: 650; letter-spacing: -.015em; }
.fb__lead { margin: 0 0 10px; font-size: 13.5px; color: var(--text-dim); }

/* Звёзды — радиокнопки: клавиатура, диктор и форма без скрипта работают
   сами. Подсветка «до выбранной включительно» — через :has(); где его нет,
   горит одна выбранная звезда, и это всё равно понятно. */
.fb-stars { display: inline-flex; gap: 2px; margin: 0 0 12px -4px; padding: 0; border: 0; }
.fb-stars__star {
  display: grid; place-items: center;
  width: 44px; height: 44px; border-radius: 10px;
  color: var(--border-strong); cursor: pointer;
  transition: color .12s, transform .12s;
}
.fb-stars__star svg { width: 30px; height: 30px; fill: currentColor; }
.fb-stars__star:hover { transform: scale(1.08); }
.fb-stars input:focus-visible + .fb-stars__star { outline: 2px solid var(--accent); outline-offset: 1px; }
.fb-stars input:checked + .fb-stars__star { color: #f5a524; }
.fb-stars:has(input:nth-of-type(2):checked) label:nth-of-type(-n+2),
.fb-stars:has(input:nth-of-type(3):checked) label:nth-of-type(-n+3),
.fb-stars:has(input:nth-of-type(4):checked) label:nth-of-type(-n+4),
.fb-stars:has(input:nth-of-type(5):checked) label:nth-of-type(-n+5) { color: #f5a524; }
/* Наведение показывает, что выберется, и перебивает выбранное. */
@media (hover: hover) {
  .fb-stars:hover label { color: var(--border-strong); }
  .fb-stars:has(label:nth-of-type(1):hover) label:nth-of-type(-n+1),
  .fb-stars:has(label:nth-of-type(2):hover) label:nth-of-type(-n+2),
  .fb-stars:has(label:nth-of-type(3):hover) label:nth-of-type(-n+3),
  .fb-stars:has(label:nth-of-type(4):hover) label:nth-of-type(-n+4),
  .fb-stars:has(label:nth-of-type(5):hover) label:nth-of-type(-n+5) { color: #f7b84b; }
}
@media (prefers-reduced-motion: reduce) { .fb-stars__star { transition: none; } .fb-stars__star:hover { transform: none; } }
.fb-stars.is-busy { opacity: .6; pointer-events: none; }

.fb__more { display: grid; gap: 10px; justify-items: start; }
.fb__more[hidden] { display: none; }
.fb__more .fld { width: 100%; }
.fb__more textarea { min-height: 64px; }
.fb__status { margin: 10px 0 0; font-size: 14px; font-weight: 550; color: #1f7a45; }
.fb__status:empty { display: none; }
.fb__error { margin: 10px 0 0; }

/* Ловушка для роботов: вне экрана, а не display:none — часть роботов
   пропускает скрытые поля, но заполняет те, что просто не видно. */
.fb__trap { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* Страница «Написать нам». */
.fbpage { max-width: 620px; padding: 26px 0 72px; }
.fbpage__form { display: grid; gap: 16px; }
.fbpage__page { margin: -4px 0 0; font-size: 12.5px; color: var(--text-faint); overflow-wrap: anywhere; }
.fbpage__page code { font-size: 12px; color: var(--text-dim); }
.fbpage__send { justify-self: start; min-width: 200px; }
.fbpage__done { text-align: center; padding: 40px 0 8px; }
.fbpage__done[hidden] { display: none; }
.fbpage .co__error { margin: 0; }
@media (max-width: 560px) { .fbpage__send { justify-self: stretch; } }

.footer__write { text-decoration: none; }
.footer__write:hover { color: var(--accent); text-decoration: underline; }
