/* Toggle Coffee storefront — premium-minimal skin on the Milano layout system.
   Typeface: Mont (self-hosted, same as menu.toggle.coffee). */

/* ---------- fonts ---------- */

@font-face{font-family:"Mont";src:url("/assets/fonts/Mont-Regular.woff2?v=2") format("woff2"),url("/assets/fonts/Mont-Regular.woff?v=2") format("woff");font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:"Mont";src:url("/assets/fonts/Mont-SemiBold.woff2?v=2") format("woff2"),url("/assets/fonts/Mont-SemiBold.woff?v=2") format("woff");font-weight:500;font-style:normal;font-display:swap}
@font-face{font-family:"Mont";src:url("/assets/fonts/Mont-SemiBold.woff2?v=2") format("woff2"),url("/assets/fonts/Mont-SemiBold.woff?v=2") format("woff");font-weight:600;font-style:normal;font-display:swap}
@font-face{font-family:"Mont";src:url("/assets/fonts/Mont-Bold.woff2?v=2") format("woff2"),url("/assets/fonts/Mont-Bold.woff?v=2") format("woff");font-weight:700;font-style:normal;font-display:swap}
@font-face{font-family:"Mont";src:url("/assets/fonts/Mont-Black.woff2?v=2") format("woff2"),url("/assets/fonts/Mont-Black.woff?v=2") format("woff");font-weight:800;font-style:normal;font-display:swap}

/* ---------- design tokens ---------- */
:root{
  --header-bg:rgb(19,19,19); --header-ink:#fff;
  --font:"Mont",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;

  --color-white:#ffffff;
  --color-gray:#f7f5f2;      /* warm paper */
  --color-gray-2:#efece7;
  --hair:#efedea;            /* lightest divider */
  --color-border:#e4e1db;

  --color-primary:#c14a3f;   /* terracotta, used sparingly */
  /* Терракота как ТЕКСТ на --color-primary-soft даёт 4.21:1 — провал WCAG 1.4.3 AA
     на кегле .9rem. Затемнённый тон того же оттенка даёт 5.55:1. Применяется только
     к тексту (ошибка формы, подпись остатка, метка изменённой строки), --color-primary
     не трогаем: на нём висят бейджи, счётчик корзины и фокусные кольца. */
  --color-primary-ink:#a63a30;
  --color-secondary:#14663f;
  --color-primary-soft:#f8ece9;
  --color-secondary-soft:#edf3ef;
  --color-heading:#141210;
  --color-text:#4a4642;
  --color-link:#141210;
  --color-link-hover:var(--color-primary);
  --color-background:#ffffff;

  --bg:var(--color-background);
  --ink:var(--color-text);
  /* Contrast pass: --muted is the lowest tone still allowed for text (~4.9:1 on paper),
     --faint is a NON-text tone (borders, dots, placeholder plates) at ~3.2:1.
     Plain hex first as a fallback for engines without color-mix(). */
  --muted:#726f6c;
  --muted:color-mix(in srgb,var(--color-text) 78%,transparent);
  --faint:#8f8c8a;
  --faint:color-mix(in srgb,var(--color-text) 62%,transparent);

  --btn-radius:999em;
  --btn-h:3rem;
  --btn-bg:#141210;
  --btn-bg-hover:#000000;
  --rounded:12px;
  --input-height:3rem;
  --tap:2.75rem;           /* 44px — minimum touch target */

  --lsp-display:-0.04em;   /* large headings */
  --lsp-head:-0.022em;     /* small headings */
  --ease:cubic-bezier(.2,.6,.2,1);
  --transition:all .5s var(--ease);

  --wrap:1180px;
  --wrap-narrow:660px;
  --pad:1.25rem;
  --section:64px;
  --hdr-h:5.5rem;          /* sticky header clearance for anchors */
}
@media (min-width:48rem){ :root{ --pad:2rem; --section:110px; } }
@media (min-width:1240px){ :root{ --pad:0rem; } }

/* Высота вьюпорта одним токеном. `svh` объявляем ТОЛЬКО через @supports: значение
   кастомного свойства парсером не проверяется, и неизвестная единица упала бы не здесь,
   а у потребителя (invalid at computed-value time) — вся высота стала бы пустой.
   Тот же приём, что у слайдера. */
:root{--sv:100vh}
@supports (height:1svh){ :root{--sv:100svh} }

/* Высота полосы про cookie ОДНИМ токеном: от неё зависят три соседа сразу —
   нижний отступ body, положение липких панелей покупки и подъём тоста.
   Величина фиксированная, чтобы раскладка была верна и БЕЗ JS; store.js
   уточняет её по факту (крупный системный шрифт, длинный перевод, 400% зум).
   Ниже 48rem текст ложится в две-три строки, а ниже 36rem кнопка уезжает на
   свою строку — отсюда запас.
   Третья ступень (36rem) добавлена по замеру: там к тексту прибавляются целая
   строка кнопки (44px) и отбивка (16px), и 6.5rem не хватало даже на короткую
   фразу — БЕЗ JS полоса накрыла бы липкую кнопку «К оформлению».
   Лучше переоценить: лишний запас — это узкая светлая полоска над плашкой,
   недооценка — недостижимая кнопка. */
:root{--ck-h:4rem}
@media (max-width:47.999rem){ :root{--ck-h:6.5rem} }
@media (max-width:36rem){ :root{--ck-h:8.5rem} }

/* ---------- base safeties ---------- */
*,*::before,*::after{box-sizing:border-box}
/* An author `display` beats the UA `[hidden]` rule (e.g. .cart{display:grid}),
   which is how an empty cart used to keep showing its totals panel. */
[hidden]{display:none!important}
/* scrollbar-gutter: место под полосу прокрутки резервируется всегда, поэтому дровер
   под lockScroll больше не дёргает страницу вбок на ширину полосы (~15px). */
html{-webkit-text-size-adjust:100%;scroll-padding-top:var(--hdr-h);scrollbar-gutter:stable}
body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--font);
  font-size:15px;line-height:1.75;font-weight:400;-webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility}
img{max-width:100%;height:auto;display:block}
a{color:var(--color-link);text-decoration:none;transition:color .3s var(--ease)}
a:hover{color:var(--color-link-hover)}
h1,h2,h3,h4{color:var(--color-heading);font-weight:700;line-height:1.12;
  letter-spacing:var(--lsp-head);margin:0 0 .5em}
h1{font-size:clamp(2.1rem,5vw,3.1rem);letter-spacing:var(--lsp-display)}
h2{font-size:clamp(1.4rem,3.4vw,1.9rem)}
h3{font-size:clamp(1.1rem,2.6vw,1.35rem)}
button{font:inherit}
::selection{background:var(--color-heading);color:#fff}
.wrap{max-width:var(--wrap);margin:0 auto;padding-inline:var(--pad)}
@media (max-width:1239px){ .wrap{padding-inline:var(--pad)} }

/* screen-reader-only text */
.vh{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap;border:0}
/* wide tables scroll inside their own box instead of the page */
.tablewrap{overflow-x:auto;-webkit-overflow-scrolling:touch;max-width:100%}

/* ---------- focus + skip link ---------- */
:focus-visible{outline:2px solid var(--color-heading);outline-offset:2px}
.btn:focus-visible,.wchip.is-on:focus-visible,.fchip.is-on:focus-visible,
.wchip[aria-checked="true"]:focus-visible,.fchip[aria-current="true"]:focus-visible,
.pager a.is-on:focus-visible{outline-color:var(--color-primary)}
/* the bar and the drawer overlay are dark — the ink outline would vanish there */
.hdr__in :focus-visible,.drawer:focus-visible{outline-color:#fff}
.main:focus{outline:none}

.skip{position:absolute;left:-999px}
.skip:focus{left:1rem;top:1rem;z-index:60;background:var(--color-heading);color:#fff;
  padding:.7rem 1.25rem;border-radius:var(--btn-radius);font-size:.85rem;font-weight:600}

.eyebrow{display:block;font-size:.6875rem;font-weight:600;text-transform:uppercase;
  letter-spacing:.22em;color:var(--muted);margin-bottom:1.1rem}

/* ---------- buttons ---------- */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:.4rem;
  height:var(--btn-h);padding:0 2rem;border:1px solid var(--btn-bg);border-radius:var(--btn-radius);
  background:var(--btn-bg);color:#fff;font-weight:600;font-size:.9rem;letter-spacing:-.01em;
  text-transform:none;cursor:pointer;transition:var(--transition);text-align:center;line-height:1}
.btn:hover{background:var(--btn-bg-hover);border-color:var(--btn-bg-hover);color:#fff}
.btn--sm{height:2.25rem;padding:0 1.15rem;font-size:.8rem}
.btn--lg{height:3.25rem;padding:0 2.5rem;width:100%;font-size:.95rem}
.btn--outline{background:transparent;color:var(--color-heading);border-color:var(--color-heading)}
.btn--outline:hover{background:var(--btn-bg-hover);color:#fff}
/* Белая пилюля для тёмной подложки (фото героя): чёрная кнопка на затемнённом
   кадре сливается со скримом и перестаёт читаться как кнопка. */
.btn--light{background:#fff;border-color:#fff;color:var(--color-heading)}
.btn--light:hover{background:var(--color-gray);border-color:var(--color-gray);
  color:var(--color-heading)}
/* disabled buttons still need to be findable by a screen reader, so no pointer-events
   trickery on aria-disabled — only the real attribute is inert */
.btn[disabled],.btn:disabled{opacity:.4;cursor:not-allowed;pointer-events:none}
.btn[aria-disabled="true"]{opacity:.4;cursor:not-allowed}

/* small neutral pill (sign-in method, misc labels) */
.chip{display:inline-block;font-size:.66rem;font-weight:600;text-transform:uppercase;
  letter-spacing:.1em;padding:.2rem .6rem;border-radius:999em;
  border:1px solid var(--color-border);color:var(--color-heading);line-height:1.6}

/* ---------- form controls ---------- */
.sel,
input[type=search],input[type=text],input[type=email],input[type=password],
input[type=tel],input[type=number],input[type=url]{
  width:100%;height:var(--input-height);padding:.55rem 1.25rem;
  border:1px solid var(--color-border);border-radius:var(--btn-radius);
  background:var(--color-background);color:var(--color-heading);font:inherit;font-size:.95rem;
  transition:border-color .3s var(--ease);appearance:none;-webkit-appearance:none}
textarea{width:100%;min-height:6rem;padding:.85rem 1.25rem;border:1px solid var(--color-border);
  border-radius:1.25rem;background:var(--color-background);color:var(--color-heading);font:inherit;
  font-size:.95rem;line-height:1.6;resize:vertical;transition:border-color .3s var(--ease)}
.sel{padding-right:2.75rem;cursor:pointer;
  background-image:url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%23141210' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:calc(100% - 20px) center}
/* NB: no `outline:none` here — the focus ring is the only cue for keyboard users. */
.sel:focus,input:focus,textarea:focus{border-color:var(--color-heading)}
.sel[disabled]{color:var(--color-heading);opacity:1;background-color:var(--color-gray);cursor:default}
::placeholder{color:var(--muted);opacity:1}
input[type=checkbox],input[type=radio]{width:1.15rem;height:1.15rem;flex:none;
  accent-color:var(--color-heading);cursor:pointer}
.field{display:block;margin:1.15rem 0}
.field__label{display:block;font-size:.7rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted);margin-bottom:.5rem}
.field__hint{display:block;font-size:.8rem;color:var(--muted);margin-top:.4rem;line-height:1.5}
.field input:disabled,.field input[readonly]{background:var(--color-gray);color:var(--color-text)}
/* Ошибка сервера стоит под своим полем, а не общей плашкой сверху: иначе непонятно,
   что именно исправлять. Тон затемнён ради контраста — см. --color-primary-ink. */
.field--err input,.field--err textarea{border-color:var(--color-primary-ink)}
.field__err{display:block;margin-top:.4rem;font-size:.8rem;line-height:1.5;color:var(--color-primary-ink)}
/* Выбор помола: подпись и компактный селект в одну строку. Полноширинная пилюля
   высотой 48px выглядела как главное поле страницы, хотя это вторичный выбор. */
.field--grind{display:flex;align-items:center;gap:.75rem;flex-wrap:wrap}
.field--grind .field__label{margin:0}
.field--grind .field__hint{flex-basis:100%;margin-top:0}
.sel--sm{width:auto;min-width:11rem;max-width:100%;height:2.5rem;font-size:.85rem;
  padding:0 2.5rem 0 1.05rem;background-color:var(--color-gray);border-color:transparent;
  background-position:calc(100% - 14px) center}
.sel--sm:hover{border-color:var(--color-border)}
.req{color:var(--color-primary)}

/* ---------- header (sticky, hairline) ---------- */
.hdr{background:var(--header-bg);border-bottom:1px solid transparent;
  position:sticky;top:0;z-index:30;transition:box-shadow .3s var(--ease),border-color .3s var(--ease)}
.hdr.is-stuck{box-shadow:0 1px 24px rgba(0,0,0,.35);border-color:transparent}
/* the bar is dark; the search panel below it keeps the light skin, so the
   inverted colours are scoped to .hdr__in rather than the whole header. */
.hdr__in{max-width:var(--wrap);margin:0 auto;padding-block:.9rem;padding-inline:var(--pad);
  display:flex;align-items:center;gap:1rem;color:var(--header-ink)}
@media (max-width:1239px){ .hdr__in{padding-inline:var(--pad)} }
.hdr__nav{display:none}
.hdr__brand{display:inline-flex;align-items:center;gap:.6875rem;flex:0 1 auto;min-width:0;
  color:var(--header-ink);transition:opacity .25s var(--ease)}
.hdr__brand:hover{opacity:.6;color:var(--header-ink)}
.hdr__badge{width:2.5rem;height:2.5rem;flex:none;border-radius:50%;display:block}
.hdr__name{font-weight:800;font-size:1.0625rem;letter-spacing:-.02em;line-height:1.1;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.hdr__actions{display:flex;align-items:center;gap:.65rem;margin-left:auto}
.ic{position:relative;background:none;border:0;color:var(--header-ink);padding:.3rem;cursor:pointer;
  display:grid;place-items:center;transition:opacity .25s var(--ease)}
.ic:hover{opacity:.55;color:var(--header-ink)}
/* Narrow screens: "Toggle Coffee" must read in full, as it does on menu.toggle.coffee,
   so the bar tightens its gaps. (Language switcher lives in the footer since 30.09.2026.) */
@media (max-width:599px){
  .hdr__in{gap:.6rem}
  .hdr__actions{gap:.4rem}
  .hdr__burger{width:1.6rem}
}
.hdr__burger{background:none;border:0;padding:.35rem;cursor:pointer;display:grid;gap:5px;width:1.9rem;
  position:relative}
.hdr__burger span{height:1.5px;background:var(--header-ink)}

.ic__count{position:absolute;top:-3px;right:-5px;min-width:15px;height:15px;padding:0 3px;
  border-radius:999px;background:var(--color-primary);color:#fff;font-size:.62rem;font-weight:700;
  line-height:15px;text-align:center}
.hdr__avatar{border-radius:50%;object-fit:cover}
.ic--cart.is-bump{animation:cartbump .45s var(--ease)}
@keyframes cartbump{30%{transform:scale(1.22)}60%{transform:scale(.94)}}

/* ---------- search ----------
   Панель лежит ВНУТРИ <header> соседом от .hdr__in. Инверсия цветов живёт на .hdr__in,
   а не на .hdr, — только поэтому панель остаётся светлой. НИ ОДНО правило этой секции
   не имеет права трогать .hdr / .hdr__in. */

/* Видимость. Без JS панель видна статически: это единственный работающий поиск в шапке,
   и обычная GET-форма на /shop/ обязана сабмититься. Блок подсказок при этом скрыт —
   без клиента он не переключается и превратился бы в мебель на каждой странице.
   Тень заменяет затемнение фона: панель читается как слой поверх, ничего не блокируя. */
.search{background:var(--color-gray);border-top:1px solid var(--hair);
  box-shadow:0 12px 28px rgba(20,18,16,.07)}
html.js .search{display:none}
html.js .search.is-open{display:block}
html:not(.js) .search__results{display:none}

/* Форма: белая пилюля с полем, очистка ВНУТРИ пилюли, «Закрыть» — снаружи. */
.search__form{max-width:var(--wrap);margin:0 auto;padding-block:.85rem;padding-inline:var(--pad);
  display:flex;align-items:center;gap:.7rem;color:var(--color-heading)}
.search__field{flex:1 1 auto;min-width:0;display:flex;align-items:center;gap:.6rem;
  height:var(--input-height);padding-inline:1rem;background:var(--color-background);
  border:1px solid var(--color-border);border-radius:var(--btn-radius);
  transition:border-color .3s var(--ease)}
/* Фокус показывает сама пилюля — чёрная граница и мягкое кольцо; у поля внутри своей
   прямоугольной обводки нет (30.09.2026: «убери выделение квадратом»). */
.search__field:focus-within{border-color:var(--color-heading);box-shadow:0 0 0 4px rgba(20,18,16,.08)}
.search__field{transition:border-color .3s var(--ease),box-shadow .3s var(--ease)}
.search__icon{flex:none;color:var(--muted);transform-origin:45% 45%;
  transition:color .3s var(--ease),transform .45s cubic-bezier(.34,1.56,.64,1)}
.search__field:focus-within .search__icon{color:var(--color-heading);transform:scale(1.08) rotate(-10deg)}
/* Глобальное правило полей (input[type=search]) задаёт padding .55rem 1.25rem, height,
   рамку и фон — внутри пилюли всё это нужно сбросить ЯВНО, иначе оно течёт внутрь.
   Ровно эта ловушка когда-то съела цифру в степпере количества.
   Кегль 16px безусловен: иначе iOS зумит в поле на 768px и обратно не отъезжает,
   а разница с прежними 1.05rem — четверть пикселя. Поле само по себе — цель 44px. */
.search__form input[type=search]{flex:1 1 auto;min-width:0;width:auto;
  height:auto;min-height:var(--tap);padding:0;border:0;background:none;border-radius:0;
  color:var(--color-heading);font-size:16px;line-height:1.25}
.search__form input[type=search]:focus{border:0}
.search__form input[type=search]:focus,.search__form input[type=search]:focus-visible{outline:none}
/* Нативный крестик WebKit стоит вплотную к нашему — два «×» подряд читаются как ошибка. */
.search__form input[type=search]::-webkit-search-cancel-button{display:none}
.search__clear{flex:none;width:var(--tap);height:var(--tap);margin-right:-.65rem;
  display:grid;place-items:center;background:none;border:0;color:var(--muted);
  font-size:1.25rem;line-height:1;cursor:pointer}
.search__clear:hover{color:var(--color-heading)}
/* Словом промахнуться труднее, чем глифом, и «очистить» с «закрыть» больше не один
   элемент на два намерения. */
.search__close{flex:none;min-height:var(--tap);padding-inline:.9rem;background:none;border:0;
  color:var(--muted);font-size:.85rem;font-weight:600;cursor:pointer}
.search__close:hover{color:var(--color-heading)}

/* Выдача. Потолок высоты БЕЗУСЛОВЕН, а не мобильный: панель лежит внутри
   position:sticky шапки, и всё, что ушло ниже вьюпорта, недостижимо ничем — ни колесом,
   ни клавиатурой (на iPad и на ноутбуке 1280×720 туда уезжали хвостовые ссылки).
   Белая поверхность идёт во всю ширину, колонку держат внутренние обёртки: тёплая
   полоса остаётся только под полем, и выдача не растворяется в сетке карточек
   каталога — у .pc__media тот же тон --color-gray. */
.search__results{background:var(--color-background);border-top:1px solid var(--hair);
  padding-block:0 .75rem;
  max-height:min(60vh,26rem);overflow-y:auto;overscroll-behavior:contain}
@supports (height:1svh){ .search__results{max-height:min(60svh,26rem)} }
/* Только padding-block + отдельный padding-inline: шорткат обнулил бы боковые отступы. */
.sr__intro,.sr__out{max-width:var(--wrap);margin:0 auto;padding-inline:var(--pad)}

/* Блок по умолчанию (категории) и заголовки групп. */
.sr__intro{padding-block:.9rem 1rem}
.sr__g{margin:0 0 .6rem;font-size:.7rem;font-weight:600;text-transform:uppercase;
  letter-spacing:.14em;color:var(--muted)}
.sr__n{color:var(--faint);letter-spacing:.06em}
.sr__chips{display:flex;flex-wrap:wrap;gap:.5rem}
.sr__chips .chip{display:inline-flex;align-items:center;gap:.35rem;
  transition:border-color .25s var(--ease),color .25s var(--ease)}
.sr__chips .chip:hover{border-color:var(--color-heading)}
.sr__group{padding-block:.6rem .4rem;border-top:1px solid var(--hair)}
.sr__group:first-child{border-top:0}
.sr__note{margin:0;padding-block:.7rem;color:var(--muted);font-size:.85rem}

/* Строка результата. Средняя колонка minmax(0,1fr) обязательна: без неё грид-ячейка
   не сжимается ниже содержимого, и длинное название выдавливает цену за край. */
.sr__row{display:grid;grid-template-columns:44px minmax(0,1fr) auto;gap:.9rem;
  align-items:center;padding-block:.5rem;min-height:var(--tap);
  color:var(--color-text);border-radius:8px}
.sr__row.is-out{opacity:.55}
.sr__media{width:44px;height:44px;flex:none;border-radius:8px;overflow:hidden;
  background:var(--color-gray)}
/* Плейсхолдер вместо фото: приглушённый самолётик из фирменного круга не годится по весу,
   поэтому — две буквы фирменным начертанием, как на карточке каталога. */
.sr__media--ph::before{content:"TC";display:grid;place-items:center;width:100%;height:100%;
  font:800 .62rem/1 var(--font);letter-spacing:.06em;color:var(--faint)}
.sr__media--wide{height:30px}          /* обложка статьи, 3/2 — ширина колонки та же,
                                          иначе тексты двух групп не встанут по вертикали */
.sr__media img{width:100%;height:100%;object-fit:cover}
.sr__body{min-width:0}
.sr__t{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
  font-weight:600;letter-spacing:-.02em;color:var(--color-heading);line-height:1.35;
  overflow-wrap:break-word}
.sr__m{display:block;margin-top:.15rem;font-size:.7rem;text-transform:uppercase;
  letter-spacing:.14em;color:var(--muted);white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis}
.sr__side{display:flex;flex-direction:column;align-items:flex-end;gap:.15rem;text-align:right}
.sr__price{font-weight:600;color:var(--color-heading);font-size:.9rem;white-space:nowrap}
.sr__stock{font-size:.68rem;text-transform:uppercase;letter-spacing:.1em;color:var(--color-primary-ink)}
.sr__off{font-size:.68rem;font-weight:700;color:var(--color-primary)}
/* Хвост группы: медиа нет, но текст обязан стоять на той же вертикали, что и названия. */
.sr__all{grid-template-columns:minmax(0,1fr);padding-left:calc(44px + .9rem)}
.sr__all .sr__t{color:var(--color-heading);font-size:.85rem}

/* Наведение и активная строка комбобокса. Активность помечена НЕ только цветом
   (фон + вертикальная черта), чтобы читалась и в монохроме. */
.sr__row:hover{color:var(--color-text);background:var(--color-gray)}
.sr__row[aria-selected="true"]{background:var(--color-gray);
  box-shadow:inset 2px 0 0 var(--color-heading)}
/* Терракота дозирована: подсвечиваем фоном, цвет текста не трогаем. */
.sr__hi{background:rgba(193,74,63,.12);color:inherit;border-radius:2px;padding:0 .12em}

/* Загрузка и пустое состояние. Скелетоны переиспользуют .sk/.sk--line/.sk--price —
   они уже уважают prefers-reduced-motion, новых анимаций не заводим. */
.search__results[aria-busy="true"]{opacity:.55;transition:opacity .2s var(--ease)}
.sr__row--sk{pointer-events:none}
.sr__sk2{width:55%;margin-top:.35rem}
.sr__empty{padding-block:.9rem 1rem}     /* ТОЛЬКО padding-block: padding-inline уже
                                            дал родитель .sr__out, иначе двойной отступ */
.sr__empty-t{margin:0;color:var(--color-heading);font-weight:600}
.sr__empty-x{margin:.35rem 0 .9rem;color:var(--muted);font-size:.85rem}
/* <mark> НЕ переопределяем: в forced-colors он сам подхватывает системные Mark/MarkText,
   а наш фон там всё равно отбрасывается. */
@media (forced-colors:active){
  .sr__row[aria-selected="true"]{background:Highlight;color:HighlightText;forced-color-adjust:none}
  .search__field{border-color:CanvasText}
  .search__field:focus-within{outline:2px solid Highlight;outline-offset:2px}
}

/* Анимации поиска (30.09.2026). Панель выезжает сверху; чипы и строки выдачи проявляются
   лесенкой (строки пересоздаются на каждый ответ — анимация идёт на новые узлы, 180 мс, не
   мельтешит); крестик очистки — пружинкой; лупа покачивается, пока ждём ответа. Всё — только
   transform/opacity, без перестройки раскладки. При prefers-reduced-motion — ничего не движется. */
@keyframes tc-search-in{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:none}}
@keyframes tc-sr-in{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
@keyframes tc-pop{from{opacity:0;transform:scale(.4) rotate(-90deg)}to{opacity:1;transform:none}}
@keyframes tc-seek{0%,100%{transform:scale(1.08) rotate(-10deg)}50%{transform:scale(1.08) rotate(14deg) translate(1px,-1px)}}
html.js .search.is-open{animation:tc-search-in .32s var(--ease) both}
.search.is-open .sr__chips .chip{animation:tc-sr-in .36s var(--ease) both}
.search.is-open .sr__chips .chip:nth-child(2){animation-delay:.03s}
.search.is-open .sr__chips .chip:nth-child(3){animation-delay:.06s}
.search.is-open .sr__chips .chip:nth-child(4){animation-delay:.09s}
.search.is-open .sr__chips .chip:nth-child(5){animation-delay:.12s}
.search.is-open .sr__chips .chip:nth-child(n+6){animation-delay:.15s}
.sr__out .sr__row{animation:tc-sr-in .18s var(--ease) both}
.sr__out .sr__row:nth-child(2){animation-delay:.025s}
.sr__out .sr__row:nth-child(3){animation-delay:.05s}
.sr__out .sr__row:nth-child(4){animation-delay:.075s}
.sr__out .sr__row:nth-child(n+5){animation-delay:.1s}
.sr__out .sr__row--sk{animation:none}
.search__clear:not([hidden]){animation:tc-pop .28s cubic-bezier(.34,1.56,.64,1) both}
.search.is-busy .search__icon{animation:tc-seek .7s var(--ease) infinite}
@media (prefers-reduced-motion:reduce){
  html.js .search.is-open,.search.is-open .sr__chips .chip,.sr__out .sr__row,
  .search__clear:not([hidden]),.search.is-busy .search__icon{animation:none}
  .search__icon,.search__field{transition:none}
  .search__field:focus-within .search__icon{transform:none}
}

/* ---------- drawer ---------- */
.drawer{position:fixed;inset:0;z-index:50;background:rgba(20,18,16,.4);backdrop-filter:blur(2px)}
.drawer__panel{position:absolute;inset:0 22% 0 0;max-width:23rem;background:#fff;color:var(--color-heading);
  padding:3.5rem 1.75rem 2rem;overflow:auto}
.drawer__close{position:absolute;top:.75rem;right:1rem;background:none;border:0;
  color:var(--color-heading);font-size:1.9rem;line-height:1;cursor:pointer}
.drawer__nav{display:flex;flex-direction:column}
.drawer__head{font-weight:600;font-size:1.05rem;letter-spacing:-.03em;padding:.85rem 0;
  border-bottom:1px solid var(--hair)}
.drawer__sub{padding:.55rem 0 .55rem .85rem;color:var(--muted);display:flex;justify-content:space-between;font-size:.95rem}
.drawer__sub:hover{color:var(--color-heading)}
.n{color:var(--muted);font-size:.8em}

/* ---------- main / hero ---------- */
.main{min-height:60vh;padding-bottom:var(--section)}
.hero{padding-block:clamp(3rem,9vw,7rem) clamp(2.5rem,6vw,4rem)}
.hero__title{font-size:clamp(2.6rem,8vw,4.75rem);font-weight:800;letter-spacing:-.045em;
  line-height:1;margin:0 0 .5rem;color:var(--color-heading)}
.hero__lead{color:var(--muted);max-width:34rem;margin:0;font-size:1.05rem;line-height:1.65}
/* margin-block-start, never padding on .hero — a padding shorthand would wipe
   the .wrap padding-inline and glue the copy to the screen edge. */
.hero__cta{display:flex;flex-wrap:wrap;gap:.75rem;margin-block-start:2rem}
.page-head{padding-block:clamp(2.5rem,6vw,4.5rem) 1.25rem}
.page-head h1{margin:.1em 0}
.page-head__note{color:var(--muted);margin:.5rem 0 0;max-width:40rem}

/* ---------- home: hero slider ---------- */
/* Полноширинная полоса. .hslider — прямой ребёнок .main, а .main ширину не ограничивает,
   поэтому полная ширина берётся сама собой: ни 100vw (даёт горизонтальную прокрутку при
   видимом скроллбаре), ни отрицательных margin не нужно. Текст внутри держит меру .wrap.
   ВНИМАНИЕ: вертикальные отступы здесь и ниже — только padding-block; шорткат padding:X 0
   обнулил бы padding-inline у .wrap и приклеил копию к краю экрана на мобильном. */
/* Высота полосы — ОДНА непрерывная функция вьюпорта, без ступеней aspect-ratio.
   125vw            — портретная ветвь: ровно прежние 4/5 на телефонах до 442px;
   30rem + 16.5vw   — пологая ветвь планшета и десктопа;
   обе прямые пересекаются в W = 442.4px, поэтому обрыва нет ПО ПОСТРОЕНИЮ;
   76svh            — потолок по высоте вьюпорта (ландшафт телефона, низкое окно, зум);
   16rem            — абсолютный пол: при 400% зума полоса может быть выше вьюпорта,
                      вертикальная прокрутка по WCAG 1.4.10 разрешена, потеря текста — нет.
   Ни одного пикселя из изображения: CLS ноль по построению, как и раньше.
   svh, а не dvh: svh не меняется при сворачивании адресной строки, полоса не «дышит».

   svh задаётся ТОЛЬКО через @supports. Приём «правило-предшественник», которым в этом
   файле лечат max-height, для кастомного свойства НЕ работает: значение custom property
   не проверяется парсером, оно принимается как поток токенов и падает уже у потребителя
   (var() → invalid at computed-value time), поэтому предыдущее объявление не спасает. */
.hslider{position:relative;
  --hs-h:max(16rem, min(125vw, 30rem + 16.5vw, 76vh));
  --hs-pad:clamp(.5rem, var(--hs-h) * .028, 1.6rem);
  --hs-air:clamp(.45rem, var(--hs-h) * .022, .85rem);
  /* Дополнительный воздух под копией. Растёт от высоты полосы и обнуляется на
     короткой: при --hs-h ≤ 18rem (ландшафт телефона, 400% зума) запас под текстом
     и так всего десятки пикселей, и любой лишний отступ вернул бы срезание. */
  --hs-foot:clamp(0px, (var(--hs-h) - 18rem) * .18, 4rem)}
@supports (height:1svh){
  .hslider{--hs-h:max(16rem, min(125vw, 30rem + 16.5vw, 76svh))}
}

/* Высота приходит ОДНИМ значением --hs-h и считается только из вьюпорта — ни одного
   пикселя из изображения, поэтому сдвига вёрстки при загрузке фото нет по построению.
   Тёмная плита под слайдами делает белый текст читаемым ещё до прихода JPEG
   (и оставляет слайд осмысленным, если фото не пришло вовсе).
   min-height — только страховка на случай, если var(--hs-h) не разрешится (движок без
   min()/max(): var() тогда даёт не предыдущее объявление, а initial, и полоса
   схлопнулась бы в ноль). width не задаётся: без aspect-ratio у блочного div ширина
   auto = полная, переносить высоту в ширину больше нечему. */
.hslider__viewport{position:relative;overflow:hidden;
  height:var(--hs-h);min-height:15rem;
  background:var(--color-heading);touch-action:pan-y pinch-zoom}

/* Слои слайдов. Переход — только opacity: горизонтальные сдвиги провоцируют overflow
   и дёрганье на мобильном. visibility убирает неактивный слайд из порядка табуляции,
   поэтому переключается без задержки при показе и с задержкой при уходе. */
.hs__slide{position:absolute;inset:0;opacity:0;visibility:hidden;--hs-scrim:.75;
  transition:opacity .6s var(--ease),visibility 0s linear .6s}
.hs__slide.is-on{opacity:1;visibility:visible;z-index:2;
  transition:opacity .6s var(--ease),visibility 0s linear 0s}
.hs__slide--soft{--hs-scrim:.62}
.hs__slide--normal{--hs-scrim:.75}
.hs__slide--strong{--hs-scrim:.88}

.hs__media{position:absolute;inset:0}
/* Точка фокуса кадрирования приходит из разметки парой кастомных свойств --hs-fx/--hs-fy
   (шаблон печатает style="--hs-fx:X%;--hs-fy:Y%"), а не готовым object-position: так
   значение по умолчанию 50%/50% живёт здесь, в CSS, и слайд без фокуса кадрируется
   одинаково и до правки шаблона, и после. */
.hs__img{width:100%;height:100%;object-fit:cover;
  object-position:var(--hs-fx,50%) var(--hs-fy,50%);display:block}

/* Копия прижата к низу. Её нижний отступ перекрывает панель управления целиком:
   высота панели (44px) + её собственный нижний отступ + воздух. Считать «на глаз»
   нельзя — при восьми слайдах ряд точек становится шире колонки текста и заезжает
   под лид, а на мобильном панель вообще стоит по центру прямо под копией. */
.hs__body{position:absolute;inset:0;z-index:1;display:grid;
  /* без явной колонки неявный трек auto = min-content содержимого: одно длинное слово
     раздувало трек и выносило всю колонку за правый край полосы */
  grid-template-columns:minmax(0,1fr);
  /* align-content:end, а НЕ safe end: safe при переполнении роняет вниз кнопку CTA
     и кладёт панель поверх лида, то есть меняет отказ на худший. Переполнения по новой
     раскладке не бывает — максимум копии ограничен line-clamp у всех четырёх элементов
     и посчитан: запас неотрицателен во всей матрице. */
  align-content:end;
  /* Видимой навигации у слайдера нет, резервировать под неё высоту больше не нужно —
     прежние 44px тач-цели вернулись тексту. Остаётся собственный нижний отступ полосы
     плюс --hs-foot: дополнительный воздух, который на короткой полосе обнуляется сам. */
  padding-block-end:calc(var(--hs-pad) + var(--hs-air) + var(--hs-foot))}
/* .wrap несёт margin:0 auto. У грид-элемента авто-поля отменяют растяжение: коробка
   сжимается по содержимому и уезжает в центр — колонка текста переставала совпадать
   с вертикалью остальных секций. width:100% возвращает меру на место. */
.hs__body>.wrap{width:100%}
.hs__copy{position:relative;min-width:0;
  /* мера копии одной формулой: 100% на телефоне, 416px на 576, 497 на 768,
     607 на 1024, потолок 38rem — те же опорные точки, что были ступенями,
     но без скачков 535→416 на 576px и 416→608 на 1024px */
  max-width:min(100%, 38rem, max(26rem, 10.5rem + 42.9vw))}
/* Тень текста контраст не решает, но снимает «звон» белого на светлом пятне кадра.
   Кегли считаются из ВЫСОТЫ ПОЛОСЫ, а не только из ширины: min(ширинный член,
   высотный член). На нормальном вьюпорте правит ширинный (монотонная лестница
   без прежних инверсий 41.6→35.2 на 576 и 48→45.1 на 1024), а на короткой полосе
   управление молча забирает высотный, и копия сама садится под доступное место:
   на 812×375 заголовок уходит с 45.5 до 26.2px, потому что полоса 285px, — без
   единого медиазапроса. Ступенчатые правила кегля на 36/48/64rem поэтому удалены. */
.hs__eyebrow{color:rgba(255,255,255,.88);
  line-height:1.35;
  margin-bottom:clamp(.25rem, var(--hs-h) * .024, .9rem);
  /* потолок в две строки: без него надзаголовок был единственным элементом копии
     без ограничения, и «худший слайд» переставал быть конечной величиной */
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;
  overflow:hidden;
  text-shadow:0 1px 22px rgba(0,0,0,.45)}
.hs__title{font-size:clamp(1.15rem, min(1rem + 2.75vw, var(--hs-h) * .083), 3.5rem);
  font-weight:800;letter-spacing:-.045em;line-height:1.05;
  margin:0 0 clamp(.25rem, var(--hs-h) * .022, .8rem);
  color:#fff;
  /* anywhere, а не break-word: только anywhere уменьшает min-content трека, поэтому
     только он реально переносит длинное слово, а не просто разрешает обрезку */
  overflow-wrap:anywhere;
  text-shadow:0 1px 22px rgba(0,0,0,.45);
  /* Иначе длинный заголовок срезается сверху молча, без многоточия. */
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3;line-clamp:3;overflow:hidden}
.hs__lead{margin:0;color:rgba(255,255,255,.88);
  font-size:clamp(.8rem, min(.72rem + .55vw, var(--hs-h) * .032), 1.05rem);
  line-height:1.5;
  overflow-wrap:anywhere;
  text-shadow:0 1px 22px rgba(0,0,0,.45);
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3;line-clamp:3;
  overflow:hidden}
/* Кнопка не растягивается на всю ширину: у нас пилюли, полноширинная выглядит чужеродно. */
.hs__cta{display:flex;flex-wrap:wrap;gap:.6rem;
  margin:clamp(.45rem, var(--hs-h) * .032, 1.5rem) 0 0}
/* .btn задаёт height:var(--btn-h)=48px — фиксированную, поэтому прежний min-height:var(--tap)
   (44px) не мог сработать ни при каких условиях, а длинная подпись кнопки вылезала из пилюли
   боком. Отпускаем высоту и разрешаем перенос: тач-цель тогда держит min-height, а не height. */
.hs__cta .btn{height:auto;min-height:var(--btn-h);padding-block:.55rem;
  line-height:1.25;max-width:100%;overflow-wrap:anywhere}

/* Панель управления лежит поверх всей полосы и сама кликов не ловит — иначе она
   перекрыла бы ссылку-кнопку слайда. Кликабельны только сами кнопки. */
.hs__ctrl{position:absolute;inset:0;z-index:3;pointer-events:none}
/* Панель — одной строкой у нижнего края, в мере .wrap: ‹ • • • › ▮▮ (порядок совпадает
   с порядком в разметке). Стрелки НЕ вынесены к краям полосы намеренно: ниже 1240px
   у .wrap боковое поле всего 32px, круглая стрелка 44px в него не помещается и наезжала
   на заголовок слайда. Держать текст в мере и одновременно ставить стрелки к краям
   на этих ширинах нельзя — выбран текст, он важнее.
   nowrap обязателен: при переносе flexbox сначала уводит кнопку паузы на вторую строку
   и только потом сжимает — панель вырастала вверх и наезжала на кнопку слайда. */
.hs__ctrl .wrap{height:100%;display:flex;align-items:flex-end;justify-content:flex-end;
  padding-block-end:var(--hs-pad)}



/* Пауза обязательна по WCAG 2.2.2: движение стартует само и длится дольше 5 с,
   наведение и фокус механизм остановки не заменяют.
   Фон .55 — по той же причине, что у стрелок: 4.38:1 с белым глифом на белом кадре. */
/* Пауза приходит из разметки с классом .vh — визуально её нет. Она обязана существовать
   по WCAG 2.2.2: движение стартует само и длится дольше 5 секунд, а наведение и фокус
   механизм остановки не заменяют. Проявляется только при переходе на неё с клавиатуры:
   .hs__play.vh:focus-visible это (0,3,0) против (0,1,0) у .vh, поэтому перебивает без
   !important. Мышью и пальцем её не достать — это и есть «навигации не видно». */
/* Скрытие обязано быть на ПАРЕ классов: .vh (0,1,0) объявлен выше по файлу, чем
   .hs__play (0,1,0), и при равной специфичности побеждает последний — кнопка оставалась
   видимой во все 44×44. .hs__play.vh это (0,2,0), оно перебивает без !important. */
.hs__play.vh{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap;border:0}
.hs__play.vh:focus-visible{position:static;width:var(--tap);height:var(--tap);margin:0;
  overflow:visible;clip-path:none;white-space:normal;
  border:1px solid rgba(255,255,255,.5)}
.hs__play{flex:none;width:var(--tap);height:var(--tap);padding:0;
  border:1px solid rgba(255,255,255,.5);border-radius:50%;
  background:rgba(12,10,9,.55);color:#fff;font-size:.62rem;line-height:1;
  letter-spacing:.06em;cursor:pointer;pointer-events:auto;display:grid;place-items:center;
  transition:background-color .3s var(--ease),border-color .3s var(--ease)}
.hs__play:hover{background:rgba(12,10,9,.62);border-color:#fff;color:#fff}
.hs__play.is-paused{background:rgba(12,10,9,.62);border-color:rgba(255,255,255,.75);
  font-size:.72rem}

/* Кольцо фокуса на фотографии — двухслойное. Одного белого мало: оно невидимо и на
   белой пилюле .btn--light, и там, где панель оказалась на светлом кадре. Второй слой —
   тёмная тень; в forced-colors тени гасятся, там работу берёт на себя блок ниже. */
.hslider :focus-visible{outline:2px solid #fff;outline-offset:3px;
  box-shadow:0 0 0 5px rgba(12,10,9,.85)}

/* Единственная ступень сжатия — по ВЫСОТЕ: ландшафт телефона, низкое окно ноутбука,
   200–400% зума. Число строк — единственная величина, которую нельзя интерполировать,
   поэтому она и осталась медиазапросом, а кегли и отступы текут сами.
   Гейт без orientation: портретный 400%-зум (192×256, 320×192, 256×341) обязан сюда
   попадать, а orientation:landscape его пропускал. min-aspect-ratio:5/8 отделяет
   «полосу ограничивает высота» от «полосу ограничивает ширина»: полоса высото-ограничена
   при W/H > 0.608, а 5/8 = 0.625 — практически та же линия. Портретные телефоны матрицы
   (280×653 = 0.43 … 430×932 = 0.46) сюда не попадают, 568×320 = 1.78 и 192×256 = 0.75 —
   попадают.
   max-height в медиазапросе меряет БОЛЬШОЙ вьюпорт, а полоса живёт на svh — и это
   осознанно: на мобильном портрете правило не сработает, и не должно (запас там
   +42…+201px даже при полных 3+3); в ландшафте iOS показывает компактную панель,
   svh ≈ lvh; при зуме на десктопе панелей нет вовсе. Гейт надёжен ровно там, где нужен.
   Проверка непрерывности: 932×545 (гейт не сработал, 3+3) даёт запас +74.6,
   932×544 (сработал, 2+2) даёт +109 — скачок только вверх, контент не теряется. */
@media (max-height:34rem) and (min-aspect-ratio:5/8){
  .hs__title{-webkit-line-clamp:2;line-clamp:2}
  .hs__lead{-webkit-line-clamp:2;line-clamp:2}
}

/* Windows High Contrast: белый текст принудительно перекрашивается в CanvasText и
   ложится поверх фотографии, которую forced-colors не убирает. Фото декоративное
   (alt=""), поэтому его и убираем — оно единственная причина, по которой текст белый.
   На геометрию это не влияет: высоту держит --hs-h, а не изображение.
   ПРОВЕРИТЬ ВРУЧНУЮ в реальном Windows High Contrast перед деплоем: поведение
   background-image (градиентов скрима) в этом режиме различается между движками. */
@media (forced-colors:active){
  .hs__media{display:none}
  .hslider__viewport{background:Canvas;border-block:1px solid CanvasText}
  .hs__title,.hs__lead,.hs__eyebrow{color:CanvasText;text-shadow:none}
  .hs__play{background:Canvas;color:CanvasText}
}

/* Пользователь попросил больше контраста — герой обязан ответить: он весь построен
   на полупрозрачностях. Скрим поднимается до верхней ступени, лид и надзаголовок
   становятся чистым белым. Числа выше при этом только улучшаются (soft .62 → .88
   даёт 14.8:1 вместо 5.6:1), то есть заявленный контраст остаётся нижней границей. */
@media (prefers-contrast:more){
  .hs__slide{--hs-scrim:.88}
  .hs__eyebrow,.hs__lead{color:#fff}
}

/* Печать. В paged media единицы вьюпорта считаются от страничного бокса, поэтому на A4
   (794×1123) полоса выходила 611px — 54% первого листа под декоративное фото и кнопки.
   На бумаге от героя нужен только текст. */
@media print{
  .hslider__viewport{height:auto;min-height:0}
  .hs__media,.hs__ctrl{display:none}
  .hs__slide{position:static;opacity:1;visibility:visible}
  .hs__slide:not(.is-on){display:none}
  .hs__body{position:static;padding-block:0}
  .hs__title,.hs__lead,.hs__eyebrow{color:#000;text-shadow:none;
    -webkit-line-clamp:none;line-clamp:none;display:block}
}

/* ---------- юридические блоки витрины ----------
   Пять классов печатались шаблонами без единого правила: блоки выпадали из ритма,
   набираясь кеглем основного текста. Все — служебные примечания, поэтому один тон. */
.checkout__terms,.acct__rights,.auth__ydata,.order__returns,.order__legal{
  font-size:.85rem;line-height:1.6;color:var(--muted)}
.checkout__terms{margin:.9rem 0 0}
.checkout__terms a,.acct__rights a,.order__returns a,.order__legal a{color:var(--color-heading);
  text-decoration:underline;text-decoration-color:var(--color-border);
  text-underline-offset:.18em}
.checkout__terms a:hover,.acct__rights a:hover,.order__returns a:hover,.order__legal a:hover{
  text-decoration-color:var(--color-heading)}
/* «Ваши данные и права» — на странице настроек аккаунта (/account/settings/), то есть
   там же, где эти данные и правят. Сноска, а не секция витрины: целый --section (110px)
   перед обязательным по 152-ФЗ текстом отбивал его в отдельный экран посреди страницы. */
.acct__rights{margin-top:2.5rem;border-top:1px solid var(--hair);padding-top:1.25rem}
.acct__rights h2{font-size:.7rem;text-transform:uppercase;letter-spacing:.14em;
  color:var(--muted);margin:0 0 .6rem}
.auth__ydata{margin:.6rem 0 0}
.order__returns,.order__legal{margin:1.25rem 0 0}

/* ---------- sections ---------- */
.sect{padding-block:calc(var(--section) / 2)}
.sect__head{display:flex;align-items:end;justify-content:space-between;gap:1rem;
  border-bottom:1px solid var(--color-heading);padding-bottom:.6rem;margin-bottom:2rem}
.sect__head h2{font-size:clamp(1.15rem,2.6vw,1.6rem);letter-spacing:-.03em;margin:0}
.sect__all{font-size:.8rem;color:var(--muted);white-space:nowrap;padding-bottom:.1rem}
.sect__all:hover{color:var(--color-heading)}
.empty{color:var(--muted);padding:2.5rem 0}

/* ---------- home: where to start / why Toggle ---------- */
/* Проекты Toggle (меню, афиша, стендап) — ряд «приложений» под слайдером. Значок — тот же
   iOS-квадрат, что у иконки сайта; на телефоне карточки строкой, с 48rem — три колонки.
   Подъём и тень только при настоящем наведении: на тач-экране :hover залипает после тапа. */
.proj{padding-block:calc(var(--section) / 2) 0}
.proj__grid{display:grid;gap:.75rem;grid-template-columns:1fr;list-style:none;margin:0;padding:0}
.proj__i{display:grid;grid-template-columns:3rem minmax(0,1fr) auto;align-items:center;column-gap:1rem;
  height:100%;min-height:5rem;padding:1rem 1.15rem;border:1px solid var(--color-border);border-radius:var(--rounded);
  background:var(--bg);color:inherit;text-decoration:none;
  transition:border-color .3s var(--ease),transform .3s var(--ease),box-shadow .3s var(--ease)}
.proj__ico{display:block;width:3rem;height:3rem}
.proj__d{display:block;font-size:.6875rem;font-weight:600;letter-spacing:.1em;text-transform:uppercase;
  color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.proj__t{display:block;margin-top:.1rem;font-weight:700;font-size:1.0625rem;letter-spacing:-.02em;
  color:var(--color-heading)}
.proj__x{display:block;margin-top:.15rem;color:var(--muted);font-size:.85rem;line-height:1.45}
.proj__go{font-size:1.1rem;color:var(--color-heading);transition:transform .3s var(--ease)}
.proj__i:focus-visible{outline:2px solid var(--color-heading);outline-offset:3px}
@media (hover:hover){
  .proj__i:hover{border-color:var(--color-heading);color:inherit;transform:translateY(-2px);
    box-shadow:0 12px 26px -16px rgba(20,18,16,.4)}
  .proj__i:hover .proj__go{transform:translateX(3px)}
}
@media (min-width:48rem){ .proj__grid{grid-template-columns:repeat(3,minmax(0,1fr));gap:1rem} }
@media (prefers-reduced-motion:reduce){
  .proj__i,.proj__go{transition:none}
  .proj__i:hover,.proj__i:hover .proj__go{transform:none}
}
.starter{padding-block:calc(var(--section) / 2) 0}
.starter__grid{display:grid;gap:1rem;grid-template-columns:1fr;list-style:none;margin:0;padding:0}
.starter__i{display:block;border:1px solid var(--color-border);border-radius:var(--rounded);
  padding:1.25rem 1.4rem;color:inherit;height:100%;
  transition:border-color .3s var(--ease),background-color .3s var(--ease)}
.starter__i:hover{border-color:var(--color-heading);color:inherit}
.starter__t{display:block;font-weight:600;letter-spacing:-.02em;color:var(--color-heading)}
.starter__x{display:block;color:var(--muted);font-size:.85rem;margin-top:.25rem;line-height:1.55}

/* ---------- catalog grid + product card ---------- */
.grid{display:grid;gap:clamp(2rem,4vw,3rem) clamp(1rem,2.5vw,2rem);grid-template-columns:repeat(2,1fr);
  list-style:none;margin:0;padding:0}
.grid--3{grid-template-columns:repeat(2,1fr)}
.grid>li{display:flex;flex-direction:column;min-width:0}
.grid>li>.pc{flex:1 1 auto}
.pc{display:flex;flex-direction:column;position:relative;min-width:0}
/* .pc__top растёт (flex:1 1 auto), поэтому низ карточки — чипы и цена — садится на один
   горизонт у всего ряда. Растянутая ссылка .pc__link::after привязана к .pc__top
   (ближайший positioned): фото, кикер, имя и ноты ведут на PDP, а .pc__buy ниже
   лежит вне зоны клика и держит свои контролы через z-index:2. */
.pc__top{position:relative;flex:1 1 auto;display:flex;flex-direction:column}
.pc__media{position:relative;aspect-ratio:1/1;background:var(--color-gray);border-radius:var(--rounded);
  overflow:hidden;display:grid;place-items:center}
.pc__media>img,.pc__media>.pc__plate{width:100%;height:100%;transition:transform .7s var(--ease)}
.pc__media>img{display:block;object-fit:cover}
/* ховер от .pc__top, а не от .pc: чипы веса и кнопка не должны шевелить фото */
.pc__top:hover .pc__media>*{transform:scale(1.03)}
/* плита-плейсхолдер: иконка пачки/футболки + подпись формата — задуманный элемент,
   а не «фото нет» */
.pc__plate{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.65rem;
  background:var(--color-gray-2);color:var(--muted)}
/* колоночный флекс, а не grid: в гриде 26% у .pc__ico считались бы от авто-трека (max-content SVG 300px),
   а не от плиты — иконка не следовала за размером карточки */
.pc__ico{width:clamp(48px,26%,88px);height:auto;fill:none;stroke:currentColor;stroke-width:1.5;
  stroke-linejoin:round;stroke-linecap:round}
.pc__plate span{font-size:.6875rem;font-weight:600;letter-spacing:.16em;text-transform:uppercase;color:var(--color-text)}
/* ровно один бейдж на карточку (печатает PHP), над фото, кликов не ловит */
.pc__badge{position:absolute;top:10px;left:10px;z-index:2;pointer-events:none}
.pc__body{padding-top:.9rem;display:flex;flex-direction:column;gap:.3rem}
.pc__body>*{overflow-wrap:break-word;min-width:0}
/* три ступени кегля в карточке: капс .6875rem · текст .8125rem · имя 1rem; цена 1.0625rem/700 */
.pc__k{font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.14em;color:var(--muted);line-height:1.4}
.pc__name{margin:0;font-size:1rem;font-weight:600;line-height:1.3;letter-spacing:-.02em;color:var(--color-heading)}
/* .pc__link:hover (0,2,0) обязателен: иначе глобальное a:hover (0,1,1) красит имя терракотой
   при наведении на любую точку .pc__top (растянутый ::after) */
.pc__link,.pc__link:hover{color:inherit;text-decoration:underline;text-decoration-thickness:1px;text-decoration-color:transparent;
  text-underline-offset:.16em;transition:text-decoration-color .3s var(--ease)}
.pc__link::after{content:"";position:absolute;inset:0;z-index:1}
/* кольцо фокуса остаётся глобальным — outline здесь не трогаем */
.pc__top:hover .pc__link,.pc__link:focus-visible{text-decoration-color:currentColor}
.pc__sub{color:var(--muted);font-size:.8125rem;line-height:1.5;
  display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.pc__fresh{font-size:.75rem;color:var(--muted);display:inline-flex;align-items:center;gap:.4rem;line-height:1.4}
.pc__fresh.is-hot{color:var(--color-secondary)}
.pc__fresh.is-hot::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor;flex:none}

/* merchandising badges */
.pdp__badges{display:flex;flex-wrap:wrap;gap:.35rem;margin-bottom:.85rem}
/* На странице товара бейджи лежат поверх фото (30.09.2026): левый верхний угол, как .pc__badge
   в каталоге. pointer-events:none — клик по кадру не должен упираться в плашку. */
.pdp__media{position:relative}
.pdp__media .pdp__badges{position:absolute;top:14px;left:14px;right:14px;z-index:2;margin:0;pointer-events:none}
.pcb{font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.1em;
  background:rgba(255,255,255,.94);color:var(--color-heading);border:1px solid var(--color-border);
  padding:.22rem .6rem;border-radius:999em;line-height:1.5}
.pcb--new,.pcb--week{border-color:var(--color-heading);color:var(--color-heading)}
/* терракота затемнённая (--color-primary-ink): 5.55:1 на белой плашке вместо 4.2:1 */
.pcb--low{border-color:var(--color-primary);color:var(--color-primary-ink)}
.pcb--microlot,.pcb--limited{border-color:var(--color-secondary);color:var(--color-secondary)}
.pcb--sale{border-color:var(--color-primary);color:var(--color-primary-ink)}
.pcb--out{border-color:var(--color-heading);color:var(--color-heading)}

/* body / acidity meters — PDP; в карточке сетки больше не печатаются */
.meters{display:flex;flex-direction:column;gap:.3rem;margin-top:.7rem}
.meters--lg{margin:1rem 0 1.25rem;gap:.45rem;max-width:18rem}
.meter{display:flex;align-items:center;gap:.6rem}
.meter__l{font-size:.66rem;text-transform:uppercase;letter-spacing:.1em;color:var(--muted);
  min-width:6.2rem}
.meter__bar{display:flex;gap:3px}
/* пустые деления — --faint (3.3:1); --color-gray-2 давал 1.18:1, на PDP их не было видно */
.meter__bar i{width:14px;height:3px;border-radius:2px;background:var(--faint);display:block}
.meters--lg .meter__bar i{width:20px;height:4px}
.meter__bar i.on{background:var(--color-heading)}

/* weight chips + buy row on the card */
/* без margin-top:auto — к низу карточку сажает рост .pc__top */
.pc__buy{position:relative;z-index:2;padding-top:.8rem;display:flex;flex-direction:column;gap:.55rem}
.wchips{display:flex;flex-wrap:wrap;gap:.35rem}
.wchip{background:none;border:1px solid var(--color-border);border-radius:999em;
  padding:.3rem .7rem;font-size:.78rem;color:var(--color-text);cursor:pointer;
  display:inline-flex;align-items:center;gap:.3rem;transition:var(--transition);line-height:1.4}
.wchip:hover{border-color:var(--color-heading);color:var(--color-heading)}
.wchip.is-on,.wchip[aria-checked="true"]{background:var(--btn-bg);border-color:var(--btn-bg);
  color:#fff;font-weight:600}
.wchip i{font-style:normal;font-size:.68rem;color:var(--color-primary);font-weight:600}
.wchip.is-on i,.wchip[aria-checked="true"] i{color:#fff;opacity:.9}
.wchips--lg .wchip{padding:.5rem 1.05rem;font-size:.85rem;flex-wrap:wrap;justify-content:center}
/* Переключатель «Этот лот» (зерно ↔ дрип): соседние форматы — ССЫЛКИ в облике крупного
   чипа веса. Класс свой, не .wchip: store.js вешает на .wchip выбор фасовки (см. product.php). */
.lotchips{display:flex;flex-wrap:wrap;gap:.35rem}
.lotchip{display:inline-flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:.3rem;
  min-height:var(--tap);padding:.5rem 1.05rem;border:1px solid var(--color-border);border-radius:999em;
  font-size:.85rem;line-height:1.4;color:var(--color-text);text-decoration:none;transition:var(--transition)}
a.lotchip:hover{border-color:var(--color-heading);color:var(--color-heading)}
.lotchip.is-on{background:var(--btn-bg);border-color:var(--btn-bg);color:#fff;font-weight:600}
.lotchip__p{display:block;flex-basis:100%;font-style:normal;font-size:.62rem;font-weight:400;
  letter-spacing:.02em;opacity:.75;margin-top:.1rem;text-align:center;color:inherit}
/* price per 100 g, printed inside the weight chip — its own line, so the
   discount marker stays next to the weight label */
.wchip__unit{display:block;flex-basis:100%;font-style:normal;font-size:.62rem;font-weight:400;
  letter-spacing:.02em;opacity:.75;margin-top:.1rem;color:inherit;text-align:center}
.wchip.is-on .wchip__unit,.wchip[aria-checked="true"] .wchip__unit{color:#fff;opacity:.8}
/* Компактный вариант для строки корзины: там чипы веса — вторичный контрол рядом
   с помолом, а не главный выбор страницы товара. */
.wchips--sm{gap:.35rem}
.wchips--sm .wchip{padding:.3rem .7rem;font-size:.75rem}
/* В карточке сетки выбранный чип — обводка, не заливка: залитый чип спорил с кнопкой
   «В корзину». Область ТОЛЬКО .pc — PDP и корзина остаются как были. */
.pc .wchip{padding:.35rem .8rem;font-size:.8125rem}
.pc .wchip.is-on,.pc .wchip[aria-checked="true"]{background:none;border-color:var(--color-heading);
  box-shadow:inset 0 0 0 1px var(--color-heading);color:var(--color-heading);font-weight:600}
.pc__row{display:flex;align-items:center;justify-content:space-between;gap:.5rem .6rem}
.pc__price{display:flex;flex-wrap:wrap;align-items:baseline;gap:.15rem .4rem;color:var(--color-heading);min-width:0;line-height:1.3}
/* цена — самый тяжёлый текст карточки */
.pc__price b{font-size:1.0625rem;font-weight:700;letter-spacing:-.02em;white-space:nowrap}
.pc__old{color:var(--muted);font-weight:400;font-size:.8125rem;text-decoration:line-through;white-space:nowrap}
.pc__wt{color:var(--muted);font-size:.8125rem;white-space:nowrap}
.pc__wh{font-size:.8125rem;font-weight:600;color:var(--color-heading);text-decoration:underline;
  text-underline-offset:.15em;min-height:2.5rem;display:inline-flex;align-items:center}
.pc__wh:hover{color:var(--color-heading)}
.pc .btn--sm{height:2.5rem;padding-inline:1.25rem;font-size:.8125rem;white-space:nowrap;flex:none}
/* распродан: гасится ТОЛЬКО медиа, имя и цена остаются в полном контрасте */
.pc--out .pc__media>*{opacity:.55;filter:grayscale(1)}
.pc--out .pc__top:hover .pc__media>*{transform:none}

/* ---------- catalogue: head, category tabs, bar ---------- */
/* только padding-block: шорткат padding обнулил бы padding-inline у .wrap */
.page-head--shop{padding-block:clamp(1.25rem,3vw,2.5rem) 0}
.shophead{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:.35rem 1rem}
.shophead h1{margin:0;font-size:clamp(1.75rem,3.5vw,2.5rem);letter-spacing:-.035em}
.shophead__n{color:var(--muted);font-size:.85rem;white-space:nowrap;font-variant-numeric:tabular-nums}
/* (0,2,0): общее .searchbar a (0,1,1) иначе перебило бы цвет и кегль */
.searchbar .searchbar__clear{display:inline-flex;align-items:center;gap:.3rem;min-height:2rem;font-size:.8125rem;
  color:var(--color-primary-ink)}
.searchbar .searchbar__clear:hover{color:var(--color-heading)}

/* текстовые вкладки категорий на hairline; телефон — одна строка с прокруткой */
/* hairline — inset-тенью, а не border: overflow-x:auto делает overflow-y auto, и вкладка с
   margin-bottom:-1px резалась по padding-box (2px подчёркивание выходило 1px + прокрутка на 1px) */
.catnav{display:flex;gap:1.75rem;margin-top:1rem;box-shadow:inset 0 -1px 0 var(--hair);
  overflow-x:auto;scrollbar-width:none;-webkit-overflow-scrolling:touch}
.catnav::-webkit-scrollbar{display:none}
.abar::-webkit-scrollbar{display:none}
.catnav__i{flex:none;display:inline-flex;align-items:center;min-height:var(--tap);font-size:.9375rem;
  font-weight:500;color:var(--muted);white-space:nowrap;border-bottom:2px solid transparent;
  transition:color .3s var(--ease),border-color .3s var(--ease)}
/* кольцо фокуса внутрь: снаружи его режет overflow скроллера */
.catnav__i:focus-visible{outline-offset:-3px}
.catnav__i:hover{color:var(--color-heading)}
.catnav__i[aria-current="page"]{color:var(--color-heading);font-weight:600;border-color:var(--color-heading)}

/* одна панель: фасеты слева, сортировка справа, одна базовая линия 2.5rem */
.shopbar{display:flex;align-items:flex-start;justify-content:space-between;gap:.5rem 1.25rem;
  padding-block:.35rem;border-bottom:1px solid var(--hair);margin-bottom:clamp(1.25rem,3vw,2rem)}

/* catalogue facets */
.facets{flex:1 1 auto;min-width:0}
.facets__btn{list-style:none;cursor:pointer;display:inline-flex;align-items:center;gap:.45rem;min-height:2.5rem;
  font-size:.8125rem;font-weight:600;color:var(--color-heading);-webkit-user-select:none;user-select:none;
  -webkit-tap-highlight-color:transparent}
.facets__btn::-webkit-details-marker{display:none}
.facets__btn::after{content:"";width:.4rem;height:.4rem;border-right:1.4px solid currentColor;
  border-bottom:1.4px solid currentColor;transform:rotate(45deg) translate(-1px,-1px);
  transition:transform .25s var(--ease)}
.facets[open]>.facets__btn::after{transform:rotate(225deg) translate(-1px,-1px)}
.facets__n{font-style:normal;font-size:.6875rem;font-weight:600;min-width:1.15rem;height:1.15rem;padding-inline:.3rem;
  display:inline-grid;place-items:center;border-radius:999em;background:var(--color-heading);color:#fff}
.facets__panel{display:flex;flex-wrap:wrap;align-items:center;gap:.4rem 1.5rem}
/* position:relative — контейнер поповера подсказки .hint p */
.facets__grp{position:relative;display:flex;flex-wrap:wrap;align-items:center;gap:.4rem .6rem;min-height:2.5rem;min-width:0}
.facets__l{font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.14em;color:var(--muted);
  display:inline-flex;align-items:center;gap:.35rem;white-space:nowrap}
.facets__chips{display:flex;flex-wrap:wrap;align-items:center;gap:.35rem}
.fchip{border:1px solid var(--color-border);border-radius:999em;padding:.32rem .8rem;
  font-size:.8125rem;color:var(--color-text);transition:var(--transition);white-space:nowrap;
  display:inline-flex;align-items:center;gap:.3rem}
.fchip:hover{border-color:var(--color-heading);color:var(--color-heading)}
.fchip.is-on,.fchip[aria-current="true"]{background:var(--btn-bg);border-color:var(--btn-bg);
  color:#fff;font-weight:600}
.fchip i{font-style:normal;color:var(--muted);font-size:.9em}
.fchip.is-on i,.fchip[aria-current="true"] i{color:rgba(255,255,255,.75)}
.fchip.is-on:hover,.fchip[aria-current="true"]:hover{color:#fff;background:var(--btn-bg-hover)}
.fchip__x{opacity:.8}
/* a facet that would give zero results: visible, but not a trap */
.fchip--0{color:var(--muted);border-color:var(--hair);pointer-events:none}
.fchip--0 i{color:var(--muted)}
/* полоса активных фильтров — прижата к панели (минус её нижний отступ) */
.abar{display:flex;flex-wrap:wrap;align-items:center;gap:.35rem .5rem;
  margin:calc(-1*clamp(1.25rem,3vw,2rem) + .5rem) 0 1.25rem}
.abar__reset{margin-left:.35rem;display:inline-flex;align-items:center;min-height:2rem;font-size:.8125rem;
  color:var(--color-primary-ink)}
.abar__reset:hover{color:var(--color-heading)}
/* на десктопе summary скрыт — панель фасетов видна всегда */
@media (min-width:48rem){.facets__btn{display:none}}

/* sort — details-popover, closes from JS; without JS stays open until a link is followed */
.sortdd{position:relative;flex:none;margin-left:auto}
.sortdd__btn{list-style:none;cursor:pointer;display:inline-flex;align-items:center;gap:.4rem;min-height:2.5rem;
  padding:.3rem .2rem;font-size:.8125rem;color:var(--color-heading);white-space:nowrap;
  -webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}
.sortdd__btn::-webkit-details-marker{display:none}
.sortdd__btn::after{content:"";width:.4rem;height:.4rem;border-right:1.4px solid currentColor;
  border-bottom:1.4px solid currentColor;transform:rotate(45deg) translate(-1px,-1px);
  transition:transform .25s var(--ease)}
.sortdd[open]>.sortdd__btn::after{transform:rotate(225deg) translate(-1px,-1px)}
.sortdd__l{color:var(--muted)}
.sortdd__btn b{font-weight:600}
.sortdd__menu{position:absolute;right:0;top:calc(100% + .25rem);z-index:20;min-width:13rem;
  display:flex;flex-direction:column;padding:.35rem;background:var(--color-white);
  border:1px solid var(--color-border);border-radius:var(--rounded);box-shadow:0 12px 32px rgba(20,18,16,.08)}
.sortdd__menu a{display:flex;align-items:center;justify-content:space-between;gap:1rem;min-height:2.5rem;
  padding:.4rem .85rem;border-radius:8px;font-size:.875rem;color:var(--color-text);white-space:nowrap}
.sortdd__menu a:hover{background:var(--color-gray);color:var(--color-heading)}
.sortdd__menu a[aria-current="true"]{color:var(--color-heading);font-weight:600}
.sortdd__menu a[aria-current="true"]::after{content:"";width:6px;height:6px;border-radius:50%;background:currentColor;flex:none}

/* jargon hint — plain <details>, popover под подписью; контейнер — .facets__grp{position:relative} (на телефоне — .facets__l) */
.hint{display:inline-block}
.hint>summary{list-style:none;cursor:pointer;position:relative;width:1.25rem;height:1.25rem;border-radius:50%;
  border:1px solid var(--color-border);color:var(--muted);font-size:.7rem;line-height:1;letter-spacing:0;
  display:grid;place-items:center}
.hint>summary::-webkit-details-marker{display:none}
/* тач-зона 44px без роста самого кружка (20px); справа короче: до первого чипа gap .6rem,
   полная -12px выносила бы клик на его край (на телефоне чипы снизу с зазором 12px — там inset:-12px, см. мобильный блок) */
.hint>summary::after{content:"";position:absolute;inset:-12px -4px -12px -12px}
.hint>summary:hover{border-color:var(--color-heading);color:var(--color-heading)}
.hint p{position:absolute;left:0;top:calc(100% + .35rem);z-index:21;width:max-content;
  max-width:min(24rem,calc(100vw - 2.5rem));margin:0;padding:.75rem .9rem;background:var(--color-white);
  border:1px solid var(--color-border);border-radius:var(--rounded);box-shadow:0 12px 32px rgba(20,18,16,.08);
  font-size:.8125rem;line-height:1.55;font-weight:400;color:var(--color-text);
  text-transform:none;letter-spacing:0;white-space:normal}

/* search state inside the catalogue */
.searchbar{display:flex;flex-wrap:wrap;align-items:center;gap:.75rem;margin:.5rem 0 1.25rem}
.searchbar b{font-weight:600;color:var(--color-heading)}
.searchbar a{color:var(--color-primary);font-size:.85rem}
.searchbar a:hover{color:var(--color-heading)}
.searchbar form{display:flex;gap:.5rem;flex:1 1 16rem;min-width:0}
.searchbar form input{flex:1 1 auto;min-width:0;height:2.5rem}
#results{scroll-margin-top:var(--hdr-h)}

/* empty state (catalogue, search, rubric) */
.emptybox{border:1px solid var(--hair);border-radius:var(--rounded);
  padding:clamp(1.75rem,5vw,3rem);text-align:center;margin:2rem 0}
.emptybox h2{font-size:1.25rem;margin-bottom:.4rem}
.emptybox p{color:var(--muted);margin:0 0 1.5rem}

/* catalogue empty state — без рамки; «Часто берут» — отдельная section.sect.sect--tight ниже */
.shopempty{padding:clamp(1.5rem,5vw,3rem) 0 0;max-width:34rem}
.shopempty__t{font-size:1.35rem;margin:0 0 .4rem}
.shopempty__x{color:var(--muted);line-height:1.6;margin:0 0 1.25rem}
.shopempty__act{display:flex;flex-wrap:wrap;gap:.6rem;margin:0}
/* (0,1,0) ниже .sect — при равной специфичности побеждает это правило */
.sect--tight{padding-block:clamp(2rem,4vw,3rem) 0}

.pager{display:flex;flex-wrap:wrap;gap:.4rem;justify-content:center;margin-top:3rem}
.pager a{min-width:2.2rem;height:2.2rem;display:grid;place-items:center;border:1px solid var(--color-border);
  border-radius:999em;font-size:.85rem;color:var(--color-text);padding-inline:.5rem}
.pager a:hover{border-color:var(--color-heading);color:var(--color-heading)}
.pager a.is-on,.pager a[aria-current="page"]{background:var(--btn-bg);border-color:var(--btn-bg);color:#fff}
/* «← Назад» / «Вперёд →»: текст, а не номер — без квадратной min-width */
.pager a.pager__nav{min-width:0;padding-inline:.9rem}

/* ---------- shop sub-nav / breadcrumbs ---------- */
.shopnav{display:flex;flex-wrap:wrap;gap:.5rem;margin:1.75rem 0 2.5rem}
.shopnav a{border:1px solid var(--color-border);padding:.5rem 1.1rem;font-size:.82rem;
  border-radius:999em;color:var(--color-text);transition:var(--transition)}
.shopnav a:hover{border-color:var(--color-heading);color:var(--color-heading)}
.shopnav a.is-on,.shopnav a[aria-current="true"]{background:var(--btn-bg);color:#fff;border-color:var(--btn-bg)}
.shopnav a.is-on:focus-visible,.shopnav a[aria-current="true"]:focus-visible{outline-color:var(--color-primary)}
.crumbs{font-size:.78rem;color:var(--muted);margin-bottom:1rem;text-transform:uppercase;letter-spacing:.08em}
.crumbs a:hover{color:var(--color-heading)}

/* ---------- product page ---------- */
.pdp{padding-block:1.5rem var(--section)}
.pdp__grid{display:grid;gap:clamp(2rem,5vw,4rem);grid-template-columns:minmax(0,1fr)}
.pdp__gallery,.pdp__info{min-width:0}
/* Кадр. На телефоне — во всю ширину, но не выше 78% ширины экрана: квадрат 335×335 на 375px
   вместе с шапкой съедал первый экран, и до цены приходилось листать. Коробка становится
   чуть шире своей высоты, cover подрезает поля над и под пачкой — сама пачка стоит в центре
   кадра и остаётся целой. На ≥48rem потолок другой (см. блок responsive): там кадр обязан
   остаться квадратом. svw — только через @supports, как --sv: неизвестная единица выбросила
   бы всё объявление.
   width:100% обязателен: при auto-ширине aspect-ratio ПЕРЕНОСИТ max-height на ширину, и
   вместо кадра во всю меру выходил квадрат 293×293, прижатый влево (замерено на 375px). */
.pdp__media{background:var(--color-gray);aspect-ratio:1/1;overflow:hidden;border-radius:var(--rounded);
  width:100%;max-height:78vw}
@supports (width:1svw){ .pdp__media{max-height:78svw} }
.pdp__media img{width:100%;height:100%;object-fit:cover}
.pdp__ph{width:100%;height:100%;display:grid;place-items:center;color:var(--color-gray-2);
  font-weight:700;letter-spacing:.16em;font-size:.7rem;text-transform:uppercase}
/* variant photo strip */
.pdp__thumbs{display:flex;gap:.5rem;margin-top:.75rem;overflow-x:auto;padding-bottom:.25rem;
  -webkit-overflow-scrolling:touch;overscroll-behavior-inline:contain;scrollbar-width:thin}
.pdp__thumbs button,.pdp__thumbs .pdp__thumb{flex:none;width:4rem;height:4rem;padding:0;
  border:1px solid var(--color-border);
  border-radius:8px;overflow:hidden;background:var(--color-gray);cursor:pointer;
  transition:border-color .3s var(--ease)}
/* Миниатюра — ссылка на полный кадр (без JS она обязана работать), а у ссылки нет
   кнопочных умолчаний: без display:block картинка внутри строчного элемента даёт
   лишний зазор под собой. */
.pdp__thumb{display:block;line-height:0}
.pdp__thumbs button:hover,.pdp__thumbs .pdp__thumb:hover{border-color:var(--color-heading)}
/* Состояние держится на ОДНОМ атрибуте — том самом, который читает скринридер.
   Прежнее правило целилось ещё и в [aria-current], который не ставит никто. */
.pdp__thumbs .is-on,.pdp__thumbs [aria-pressed="true"]{border-color:var(--color-heading);
  box-shadow:0 0 0 1px var(--color-heading)}
.pdp__thumbs img{width:100%;height:100%;object-fit:cover}
.pdp__title{font-size:clamp(1.8rem,4.5vw,2.6rem);font-weight:700;letter-spacing:-.035em;margin:0 0 .3rem}
.pdp__sub{color:var(--muted);margin:0 0 1.5rem;font-size:1.05rem}
/* Ноты вкуса под заголовком — ссылки на каталог с той же нотой. Пилюли тоньше и светлее
   чипов веса: это навигация «найти похожее», а не выбор покупки, и спорить с фасовкой ниже
   им нельзя. Цвет по наведению — (0,2,0), иначе общее a:hover (0,1,1) красило бы терракотой. */
.pdp__notes{display:flex;flex-wrap:wrap;gap:.4rem;list-style:none;margin:.65rem 0 1.4rem;padding:0}
.pdp__notes li{margin:0}
.note{display:inline-flex;align-items:center;min-height:2rem;padding:.25rem .95rem;
  border:1px solid var(--color-border);border-radius:999em;background:var(--color-white);
  font-size:.875rem;line-height:1.3;color:var(--color-text);transition:var(--transition)}
.note:hover,.note:focus-visible{border-color:var(--color-heading);color:var(--color-heading)}
.pdp__country{display:block;font-size:.7rem;text-transform:uppercase;letter-spacing:.18em;
  color:var(--muted);margin-bottom:.5rem}
/* roast freshness + stock state */
.pdp__fresh{display:inline-flex;align-items:center;gap:.4rem;margin:.25rem 0 .75rem;
  font-size:.78rem;font-weight:600;letter-spacing:.02em;color:var(--color-secondary)}
.pdp__fresh--hot{background:var(--color-secondary-soft);border-radius:999em;padding:.3rem .8rem}
/* Цена и наличие — одна строка над выбором фасовки: слева цена, под ней цена за 100 г,
   справа пилюля статуса. Цена — самый тяжёлый текст колонки после заголовка (Mont Black);
   цифры табличные, чтобы при переключении 200 г ↔ 1 кг строка не прыгала по ширине.
   Hairline сверху отделяет «про кофе» от «купить». На телефоне пилюля всегда стоит своей
   строкой под ценой (.pdp__pricebox во всю ширину, блок mobile) — иначе она падала вниз
   только при появлении старой цены, и страница прыгала под пальцем. */
.pdp__buyhead{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;
  gap:.85rem 1.25rem;margin:1.5rem 0 .35rem;padding-top:1.4rem;border-top:1px solid var(--hair)}
.pdp__pricebox{min-width:0}
.pdp__price{display:flex;flex-wrap:wrap;align-items:baseline;gap:.25rem .8rem;margin:0;
  color:var(--color-heading);line-height:1}
.pdp__price b{font-size:clamp(2.25rem,1.6rem + 1.4vw,2.75rem);font-weight:800;letter-spacing:-.03em;
  font-variant-numeric:tabular-nums;white-space:nowrap}
.pdp__old{color:var(--muted);font-weight:400;font-size:1.05rem;letter-spacing:-.01em;white-space:nowrap;
  text-decoration:line-through;text-decoration-thickness:1px;font-variant-numeric:tabular-nums}
.pdp__unit{font-size:.8125rem;color:var(--muted);margin:.6rem 0 0;line-height:1.4;font-variant-numeric:tabular-nums}
/* Пилюля наличия. Точка — currentColor с мягким ореолом: в состоянии «в наличии» она того же
   тёмно-зелёного, что и текст. Контраст текста: зелёный на своей подложке ≈ 6.2:1, чернильная
   терракота ≈ 5.5:1, --muted на бумаге ≈ 4.9:1 — все выше 4.5:1 на кегле 14px. */
.pdp__stock{display:inline-flex;align-items:center;gap:.55rem;margin:0;padding:.55rem 1.1rem;
  border-radius:999em;font-size:.875rem;font-weight:600;line-height:1.3;white-space:nowrap;
  background:var(--color-secondary-soft);color:var(--color-secondary)}
.pdp__stock::before{content:"";width:.5rem;height:.5rem;border-radius:50%;flex:none;
  background:currentColor;box-shadow:0 0 0 3px color-mix(in srgb,currentColor 18%,transparent)}
.pdp__stock--low{background:var(--color-primary-soft);color:var(--color-primary-ink)}
.pdp__stock--low::before{background:var(--color-primary)}
.pdp__stock--out{background:var(--color-gray);color:var(--muted)}
.pdp__stock--out::before{background:var(--faint);box-shadow:none}
.pdp__buy{display:flex;gap:.75rem;align-items:stretch;margin-top:.75rem}
.pdp__buy .btn--lg{flex:1;width:auto}

/* Описание и характеристики — секция под покупкой. Одна колонка по умолчанию (описание
   первым), две — на ≥64rem и только с модификатором --two, который ставит сервер, когда
   есть ОБЕ половины. Заголовки — .pdp__h с (0,2,0): .prose h2 (0,1,1) иначе раздул бы
   заголовок описания до 1.5rem, а ## из самого описания остаются обычными h2 прозы. */
.pdp__more{display:grid;grid-template-columns:minmax(0,1fr);gap:clamp(2.5rem,5vw,3.5rem);
  margin-top:clamp(2.5rem,6vw,4.5rem);padding-top:clamp(1.75rem,4vw,2.75rem);border-top:1px solid var(--hair)}
.pdp__desc,.pdp__specs{min-width:0}
.pdp__specs{max-width:var(--wrap-narrow)}
.pdp__more .pdp__h{font-size:.8125rem;font-weight:700;text-transform:uppercase;letter-spacing:.16em;
  line-height:1.4;color:var(--color-heading);margin:0 0 1.25rem}
/* Таблица характеристик: капс-подпись приглушённая слева, значение справа тёмным, hairline
   между строками. vertical-align:baseline — подпись 11px и значение 15px стоят на одной
   линии, а не прилипают к верху ячейки. */
.specs{border-collapse:collapse;width:100%;margin:0;font-size:.9375rem;line-height:1.5}
.specs th,.specs td{vertical-align:baseline;padding:.8rem 0;border-bottom:1px solid var(--hair)}
.specs tr:first-child th,.specs tr:first-child td{border-top:1px solid var(--hair)}
.specs th{width:42%;padding-right:1rem;text-align:left;color:var(--muted);font-weight:600;
  text-transform:uppercase;letter-spacing:.1em;font-size:.6875rem}
.specs td{text-align:right;color:var(--color-heading);font-weight:500;font-variant-numeric:tabular-nums}

/* Статья о лоте — одна карточка-ссылка после описания. Кадр абсолютом внутри .lstory__media:
   иначе портретная обложка (1050×1400) своим естественным ростом вытянула бы строку грида
   до 650px. На ≥48rem кадр слева 5/12, текст справа — эти доли и стоят в sizes шаблона.
   Без обложки — одна текстовая колонка (.lstory--text). */
.pdp__story{margin-top:clamp(2.5rem,6vw,4rem)}
.lstory{display:grid;grid-template-columns:minmax(0,1fr);overflow:hidden;color:inherit;
  background:var(--color-gray);border-radius:var(--rounded);transition:background-color .3s var(--ease)}
.lstory:hover{color:inherit;background:var(--color-gray-2)}
.lstory__media{position:relative;display:block;aspect-ratio:16/10;overflow:hidden;background:var(--color-gray-2)}
.lstory__media img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  transition:transform .7s var(--ease)}
.lstory:hover .lstory__media img{transform:scale(1.03)}
.lstory__body{display:flex;flex-direction:column;align-items:flex-start;gap:.6rem;
  padding:clamp(1.25rem,4vw,2.75rem)}
/* кикер — чернильной терракотой: ≈5.2:1 на бумаге, чистая терракота дала бы 4.2:1 */
.lstory__k{font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.18em;
  line-height:1.5;color:var(--color-primary-ink)}
.lstory__t{font-size:clamp(1.3rem,1rem + 1.2vw,1.85rem);font-weight:700;letter-spacing:-.03em;
  line-height:1.15;color:var(--color-heading);text-wrap:balance}
.lstory__x{max-width:36rem;color:var(--color-text);font-size:.95rem;line-height:1.6;
  display:-webkit-box;-webkit-line-clamp:3;line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
.lstory__go{margin-top:.4rem;display:inline-flex;align-items:center;gap:.4rem;font-size:.875rem;
  font-weight:600;color:var(--color-heading);border-bottom:1px solid currentColor;padding-bottom:.1rem}
/* стрелка — inline-block: transform у строчного элемента не работает вовсе */
.lstory__go span{display:inline-block;transition:transform .3s var(--ease)}
.lstory:hover .lstory__go span{transform:translateX(.25rem)}
@media (min-width:48rem){
  .lstory{grid-template-columns:minmax(0,5fr) minmax(0,7fr)}
  .lstory--text{grid-template-columns:minmax(0,1fr)}
  .lstory__media{aspect-ratio:auto;min-height:16rem}
  .lstory__body{justify-content:center}
}
@media (forced-colors:active){
  /* Фоны гасятся: пилюле и карточке нужна рамка, точке — системный цвет текста. */
  .pdp__stock,.lstory{border:1px solid}
  .pdp__stock::before{forced-color-adjust:none;background:CanvasText;box-shadow:none}
}
.qty{display:inline-flex;align-items:center;height:3.25rem;box-sizing:border-box;
  border:1px solid var(--color-border);border-radius:999em;overflow:hidden;flex:none}
.qty button{width:2.4rem;height:100%;background:none;border:0;font-size:1.05rem;cursor:pointer;color:var(--color-heading)}
.qty button:hover{background:var(--color-gray)}
/* NB: padding MUST stay 0 here — the global input padding eats the whole content box
   of a ~38px field and the digit disappears. */
.qty input{width:2.4rem;min-width:0;height:100%;padding:0;border:0;border-radius:0;
  text-align:center;font:inherit;font-weight:600;color:var(--color-heading);background:none;
  -moz-appearance:textfield}
.qty input::-webkit-outer-spin-button,.qty input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
/* .qty обрезает содержимое (overflow:hidden + скругление), поэтому кольцо со сдвигом
   наружу оказывается под обрезкой и кнопка выглядит нефокусируемой. */
.qty input:focus-visible,.qty button:focus-visible{outline-offset:-2px}
/* Главная кнопка страницы товара: заметный отклик на наведение и нажатие. */
.pdp__buy .btn--lg{transition:background-color .3s var(--ease),border-color .3s var(--ease),
  box-shadow .25s var(--ease),transform .12s var(--ease)}
.pdp__buy .btn--lg:hover{box-shadow:0 8px 22px rgba(20,18,16,.18)}
.pdp__buy .btn--lg:active{transform:translateY(1px);box-shadow:none}
.pdp__buy .btn--lg[disabled]{box-shadow:none;transform:none}
@media (prefers-reduced-motion:reduce){
  .pdp__buy .btn--lg{transition:background-color .3s var(--ease)}
  .pdp__buy .btn--lg:active{transform:none}
}
/* «В корзине ✓» — вспышка после нажатия (класс ставит store.js flashAdded на 1.6 с) на ВСЕХ
   кнопках «В корзину»: страница товара и карточки сетки. Было зелёное --color-secondary,
   стало чёрно-красное: фон в 2.5 раза шире кнопки, и анимация ведёт его позицию слева
   направо — цвет кнопки (#141210) уезжает, въезжает терракота бренда и темнеет до винного.
   Кольцо терракотой — «пульс». Белый текст держит ≥4.8:1 на всём пути градиента.
   border прозрачный + background-origin:border-box: иначе фон начинался бы от padding-box,
   и под рамкой в 1px светился бы край соседней плитки.
   Селектор (0,2,0)/(0,3,0) и стоит НИЖЕ .btn:hover и .btn--outline: при равной специфичности
   решает порядок, а шорткат background у :hover обнулил бы градиент при наведении. */
.btn.is-added,.btn.is-added:hover{color:#fff;border-color:transparent;background-color:#141210;
  background-image:linear-gradient(110deg,#141210 0%,#141210 30%,var(--color-primary) 70%,#6e1a14 100%);
  background-size:250% 100%;background-position:100% 0;background-repeat:no-repeat;
  background-origin:border-box;
  animation:tc-added-sweep 1.2s var(--ease) both,tc-added-pulse 1.2s ease-out}
@keyframes tc-added-sweep{from{background-position:0 0}to{background-position:100% 0}}
@keyframes tc-added-pulse{
  0%{box-shadow:0 0 0 0 rgba(193,74,63,.55)}
  70%{box-shadow:0 0 0 .65rem rgba(193,74,63,0)}
  100%{box-shadow:0 0 0 0 rgba(193,74,63,0)}
}
@media (prefers-reduced-motion:reduce){
  /* Без движения — градиент стоит целиком, от чёрного к красному, без сдвига и кольца.
     Общий блок motion в конце файла гасит animation и сам; здесь — статичный вид. */
  .btn.is-added,.btn.is-added:hover{animation:none;background-size:100% 100%;background-position:0 0}
}
@media (forced-colors:active){
  /* Градиент в режиме высокой контрастности не виден — системная пара Highlight. */
  .btn.is-added,.btn.is-added:hover{forced-color-adjust:none;background:Highlight;color:HighlightText;
    border-color:Highlight;animation:none}
}

/* "Russian only for now" note */
.langnote{display:inline-block;font-size:.72rem;text-transform:uppercase;letter-spacing:.12em;
  color:var(--muted);border:1px solid var(--color-border);border-radius:999em;
  padding:.25rem .7rem;margin-bottom:1.25rem}

/* ---------- prose ---------- */
.prose{max-width:var(--wrap-narrow);color:var(--color-text);font-size:1.0625rem;line-height:1.8}
.prose>p:first-of-type{font-size:1.2rem;line-height:1.65;color:var(--color-heading)}
.prose p{margin:0 0 1.5em}
.prose h2{font-size:1.5rem;margin:1.8em 0 .5em}
.prose h3{font-size:1.2rem;margin:1.5em 0 .5em}
.prose ul{margin:0 0 1.5em;padding-left:1.2rem}
.prose li{margin:.3em 0}
.prose em{font-style:italic;color:var(--muted)}
.prose a{color:var(--color-heading);text-decoration:underline;text-decoration-color:var(--color-border);
  text-underline-offset:3px;text-decoration-thickness:1px}
.prose a:hover{text-decoration-color:var(--color-heading)}

/* ---------- prose blocks from mdlite: photo, photo row, quote, numbered list ----------
   Editor spec §7. Additions only — nothing above this comment is edited.
   The same four rulesets serve every .prose on the site: article, page, contact page
   and the product description. */

/* photo with a caption */
.prose .fig{margin:2.5rem 0}
/* mdlite emits no width/height (it would cost a disk read per picture per render),
   so the BOX reserves the space instead: a fixed ratio means the text below never
   jumps when a lazy photo arrives. `contain` over `cover` — an editorial photo must
   not be cropped by the engine; a portrait shot simply sits on a warm mat. */
.prose .fig img{display:block;width:100%;height:auto;aspect-ratio:3/2;object-fit:contain;
  background:var(--color-gray);border-radius:var(--rounded)}
.prose .fig figcaption{margin-top:.7rem;font-size:.85rem;line-height:1.5;color:var(--muted)}

/* a row of 2-3 photos: cropped to one height, otherwise mixed formats tear the bottom edge */
.prose .figrow{display:grid;gap:.75rem;margin:2.5rem 0}
.prose .figrow .fig{margin:0}
.prose .figrow .fig img{aspect-ratio:4/3;object-fit:cover}
.prose .figrow .fig figcaption{margin-top:.5rem;font-size:.8rem}
@media (min-width:40rem){
  .prose .figrow--2{grid-template-columns:1fr 1fr}
  .prose .figrow--3{grid-template-columns:repeat(3,1fr)}
}

/* quote with attribution — the only terracotta inside an article, and only a 2px hair.
   No fill and no italic: `.prose em` already owns italic for photo captions. */
.prose .quote{margin:2.5rem 0;padding-left:1.4rem;border-left:2px solid var(--color-primary)}
.prose .quote p{font-size:1.15rem;line-height:1.6;color:var(--color-heading);margin:0}
.prose .quote cite{display:block;margin-top:.9rem;font-style:normal;font-size:.78rem;
  text-transform:uppercase;letter-spacing:.08em;color:var(--muted)}
/* a quote that is nothing but an attribution (a caption under the block above it) */
.prose .quote cite::before{content:"— "}
.prose .quote cite:first-child{margin-top:0}
/* the parser eats the dash so the author need not remember which one to type */
.prose .quote cite::before{content:"— "}

/* numbered list — deeper than ul: the marker sits OUTSIDE the content box, so at the
   1.4rem of a bullet list a two-digit "10." hangs left of the text measure. 2rem holds
   three digits, which is more than any article here will ever number. */
.prose ol{margin:0 0 1.5em;padding-left:2rem}
.prose ol li{margin:.3em 0}
.prose ol li::marker{font-variant-numeric:tabular-nums;color:var(--muted)}

/* Wide screens only: a photo steps symmetrically outside the 660px measure, and its
   caption stays flush with the picture, not with the text. Only direct children of a
   narrow centred document — .pdp__desc and .cnote also carry .prose, and there
   a negative margin would shove the photo out of its column. Below 60rem there is no
   step-out at all: negative margins on a phone drag the picture under the screen edge
   (the same trap that bans `padding: X 0` on section wrappers). */
@media (min-width:60rem){
  .article>.prose>.fig,.article>.prose>.figrow,
  .doc>.prose>.fig,.doc>.prose>.figrow{margin-inline:-4rem}
}

/* phone: no step-out, less air, smaller captions */
@media (max-width:39.999rem){
  .prose .fig,.prose .figrow{margin-block:1.75rem}
  .prose .fig figcaption{font-size:.8rem}
  .prose .figrow{gap:.6rem}
  .prose .quote{margin-block:1.75rem;padding-left:1rem}
  .prose .quote p{font-size:1.05rem}
}

/* ---------- documents / articles ---------- */
.doc{padding-block:clamp(1.5rem,4vw,3rem) var(--section);max-width:var(--wrap-narrow)}
.doc--narrow{max-width:56rem}
.doc--center{text-align:center;max-width:34rem;margin-inline:auto}
.doc h1{margin-bottom:.6em}
/* Корзина и оформление — не «документ»: в 660px колонка списка сжимается до ~244px,
   и степпер налезает на цену. Ширину даёт .wrap, здесь только вертикальный ритм.
   Нижний padding-block — 0, как у .acctpage: воздух до подвала дают .main и .foot, а
   третий такой же слой давал ~330px пустоты перед подвалом. */
.shopflow{padding-block:clamp(1.5rem,4vw,3rem) 0}
.shopflow h1{margin-bottom:.6em}
/* closing CTA on plain CMS pages */
.pageend{margin-top:3rem;border-top:1px solid var(--hair);padding-top:2rem;
  display:flex;flex-wrap:wrap;gap:.75rem}
/* ---------- юридический документ (шаблон page_legal) ----------
   Ширину и типографику даёт .wrap + .doc + .prose — здесь только то, чего у обычной
   страницы нет: плашка черновика, строка редакции, оговорка о языке и ритм разделов.
   Вертикальные отступы — ТОЛЬКО padding-block: шорткат `padding: X 0` обнулил бы
   padding-inline у .wrap, и на телефоне текст прилип бы к краям экрана. */
.legal__draft{background:color-mix(in srgb,var(--color-primary) 8%,#fff);
  border-left:3px solid var(--color-primary);border-radius:.5rem;
  padding:.9rem 1.1rem;margin-block:1.25rem;font-size:.9rem;line-height:1.6;
  color:var(--color-text)}
.legal__draft p{margin:0}
.legal__draft p+p{margin-top:.4rem}
/* Строка редакции — та же мелкая капитель, что у eyebrow и меток на карточках. */
.legal__rev{font-size:.72rem;text-transform:uppercase;letter-spacing:.1em;
  color:var(--faint);margin-block:.75rem 1.5rem}
/* Оговорка о приоритете русской редакции: печатается только на EN, поэтому обрамление
   тонкими линиями — она обязана читаться как служебная, а не как первый абзац. */
.legal__note{font-size:.85rem;line-height:1.6;color:var(--muted);
  border-block:1px solid var(--hair);padding-block:.6rem;margin-block:1.25rem}
/* Документ длинный: разделы должны читаться как разделы, а не сливаться в полотно. */
.legal .prose h2{margin-top:2.25rem}
.legal .prose li{margin-block:.3rem}

.article{padding-block:clamp(1.5rem,4vw,3rem) var(--section);max-width:var(--wrap-narrow)}
.article__head{margin:1.5rem 0 2rem}
.article__k,.ac__k,.story__k{display:inline-block;font-size:.66rem;text-transform:uppercase;
  letter-spacing:.18em;color:var(--muted);margin-bottom:.9rem}
.article__k a,.ac__k a,.story__k a{color:inherit}
.article__k a:hover,.ac__k a:hover,.story__k a:hover{color:var(--color-heading)}
.article__head h1{font-size:clamp(1.9rem,5vw,3rem);margin:0 0 .5rem}
.article__meta{color:var(--muted);font-size:.8rem;text-transform:uppercase;letter-spacing:.06em}
/* fixed ratio: the cover no longer shoves the text down when it loads */
.article__nav{display:flex;justify-content:space-between;gap:1.5rem;margin-top:3.5rem;
  border-top:1px solid var(--hair);padding-top:1.5rem;font-size:.9rem}
.article__nav a:hover{color:var(--color-heading)}
.article__nav .article__k{margin-bottom:.25rem}
.article__next{margin-left:auto;text-align:right}

.ac{display:flex;flex-direction:column;color:inherit;height:100%}
.ac__media{aspect-ratio:3/2;background:var(--color-gray);overflow:hidden;margin-bottom:1rem;
  border-radius:var(--rounded);display:grid;place-items:center}
.ac__media img{width:100%;height:100%;object-fit:cover;transition:transform .7s var(--ease)}
.ac:hover .ac__media img{transform:scale(1.035)}
/* coverless card: a typographic plate instead of a fifth grey rectangle */
.ac__ph{color:var(--muted);font-weight:700;letter-spacing:.16em;font-size:.7rem;text-transform:uppercase;
  padding:1.25rem;text-align:center;align-self:center;max-width:100%}
.ac__ph b{display:block;margin-top:.6rem;font-size:1.05rem;font-weight:700;letter-spacing:-.025em;
  text-transform:none;line-height:1.3;color:var(--color-heading)}
.ac__t{font-size:1.05rem;font-weight:600;letter-spacing:-.025em;margin:.2rem 0;color:var(--color-heading)}
.ac__t a:not(:hover){color:inherit}
.ac__meta{color:var(--muted);font-size:.76rem;margin:0 0 .4rem;text-transform:uppercase;letter-spacing:.06em}
.ac__x{color:var(--muted);font-size:.9rem;margin:0;line-height:1.6}

.cards2{display:grid;gap:1.75rem;grid-template-columns:1fr;list-style:none;margin:0;padding:0}
.cards2>li{display:flex;flex-direction:column;min-width:0}
.story{display:flex;flex-direction:column;color:inherit}
.story img{aspect-ratio:16/10;object-fit:cover;background:var(--color-gray);
  border-radius:var(--rounded);margin-bottom:1rem;transition:transform .7s var(--ease)}
.story:hover img{transform:scale(1.03)}
.story__t{font-weight:600;font-size:1.05rem;letter-spacing:-.025em;color:var(--color-heading)}
.story__x{color:var(--muted);font-size:.88rem;margin-top:.35rem}

/* ---------- contact ----------
   Страница контактов — не «документ»: в 660px объявленные две колонки давали 253 и 316px,
   и ни один блок крупнее списка туда не влезал. Ширину даёт .wrap, здесь только
   вертикальный ритм. ТОЛЬКО padding-block: шорткат padding:X 0 обнулит padding-inline
   у .wrap и приклеит контент к краю экрана на мобильном.
   Брейкпоинтов у страницы нет: пороги деления следуют из ширины КОНТЕЙНЕРА через
   auto-fit + minmax(min(100%,Nrem),1fr). min(100%,…) обязателен — без него трек не
   схлопывается ниже N и на 320px появляется горизонтальная прокрутка. */
.contactpage{padding-block:clamp(1.5rem,4vw,3rem) var(--section)}

/* шапка-герой: герой сделан типографикой, а не фотографией, поэтому деградировать нечему */
.chead{position:relative;isolation:isolate;border-bottom:1px solid var(--hair);
  padding-bottom:clamp(1.5rem,4vw,2.5rem);margin-bottom:clamp(1rem,3vw,2rem)}
/* h1 на этой странице — служебное слово («Контакты»), а не содержание: героем идёт адрес.
   Глобальный h1 даёт clamp(2.1rem,5vw,3.1rem) и на 1440px выходил 49.6px против 46.4px
   у адреса — два дисплейных блока подряд конкурировали, и выигрывал ярлык. */
.chead h1{margin-bottom:.35em;font-size:clamp(1.15rem,2.2vw,1.5rem);font-weight:700;
  letter-spacing:var(--lsp-head);color:var(--muted)}
.chead__addr{margin:0 0 .9rem;font-weight:800;line-height:1.06;letter-spacing:-.035em;
  color:var(--color-heading);font-size:clamp(1.7rem,4.6vw,2.9rem)}
/* Две смысловые строки вместо жадного переноса: на 360px адрес одной строкой рвётся
   по случайному месту, а на 375px во второй строке остаётся одинокое «46». */
.chead__city,.chead__street{display:block}
/* город тише улицы: улица и есть то, что ищут глазами */
.chead__city{color:var(--muted);font-size:.62em;font-weight:700;letter-spacing:-.02em;margin-bottom:.15em}
.chead__hours{margin:0;font-size:.72rem;text-transform:uppercase;letter-spacing:.14em;
  color:var(--muted);display:flex;flex-wrap:wrap;align-items:center;gap:.5rem .9rem}
.cstatus{display:inline-flex;align-items:center;gap:.45rem;color:var(--muted)}
.cstatus--open{color:var(--color-heading)}
/* Точка — тёмно-зелёная, НЕ терракотовая: терракота на этой странице занята стрелкой
   карточки, и второй акцент рассыпал бы иерархию. */
.cstatus__dot{width:7px;height:7px;border-radius:50%;background:var(--faint);flex:none}
.cstatus--open .cstatus__dot{background:var(--color-secondary)}
.chead__note{margin:.7rem 0 0;font-size:.85rem;color:var(--muted)}
.chead__act{display:flex;flex-wrap:wrap;gap:.6rem;margin:clamp(1.25rem,3vw,1.75rem) 0 0}

.csect{padding-block:calc(var(--section) / 2) 0}
/* Порог деления следует из ширины КОНТЕЙНЕРА, а не окна: ландшафт телефона 568×320
   получает две колонки сам, без единого медиазапроса. */
.cfind{display:grid;gap:clamp(1.75rem,4vw,2.5rem);
  grid-template-columns:repeat(auto-fit,minmax(min(100%,20rem),1fr));align-items:start}
.cfacts{margin:0}
.cfacts dt{font-size:.7rem;text-transform:uppercase;letter-spacing:.14em;color:var(--muted);margin-top:1.5rem}
/* ОТДЕЛЬНЫМ правилом, не через :not() — :not() поднял бы специфичность до (0,2,0),
   и это уже ломало чекбоксы в админке. */
.cfacts dt:first-of-type{margin-top:0}
/* overflow-wrap по образцу .cl__name: адрес и почту правит владелец через админку,
   и одно длинное значение иначе даёт горизонтальную прокрутку на 320px. */
.cfacts dd{margin:.25rem 0 0;font-size:1.0625rem;color:var(--color-heading);overflow-wrap:break-word}
/* Тач-цель добираем ТОЛЬКО по вертикали (min-width в шапке уже обрезал «Toggle Coffee»),
   отрицательный margin-block возвращает прежний вертикальный ритм. */
.cfacts dd a{display:inline-flex;align-items:center;min-height:var(--tap);margin-block:-.45rem}
.cfacts dd a[href^="mailto:"]{overflow-wrap:anywhere}
.cfacts__city,.cfacts__street{display:block}

/* Плита с настоящим содержанием вместо серого плейсхолдера. Это же и зарезервированное
   место: появится снимок двери или встраиваемая карта — встанут сюда без правок раскладки. */
/* Колонка фактов и слот: без min-width:0 длинная строка распирает грид-трек. */
.cfind__facts,.cfind__slot{min-width:0}
.cplate{border:1px solid var(--color-border);border-radius:var(--rounded);background:var(--color-gray);
  padding:clamp(1.5rem,4vw,2rem);display:flex;flex-direction:column;justify-content:center;
  gap:1.1rem;min-height:13rem;height:100%}
.cplate__addr{margin:0;font-size:1.15rem;font-weight:700;letter-spacing:-.02em;
  color:var(--color-heading);line-height:1.3}
.cplate__act{margin:0;display:flex;flex-wrap:wrap;align-items:center;gap:.75rem 1.1rem}
/* Подчёркивание обязательно: ссылка, отличающаяся от текста только цветом,
   не читается при протанопии. */
.cplate__map{font-size:.85rem;color:var(--color-heading);text-decoration:underline;
  text-decoration-color:var(--color-border);text-underline-offset:3px;
  display:inline-flex;align-items:center;min-height:var(--tap)}
.cplate__map:hover,.cplate__map:focus-visible{text-decoration-color:var(--color-heading)}

/* карточки каналов: одна карточка = ОДНО действие, иначе растянутая ссылка съест соседние */
.ccards{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(min(100%,17rem),1fr));
  list-style:none;margin:0;padding:0}
.ccards>li{display:flex;min-width:0}
/* Паддинг 1.4rem сам даёт цель ≈76px по вертикали — --tap добирать не нужно. */
.ccard{position:relative;display:flex;flex-direction:column;gap:.25rem;width:100%;
  border:1px solid var(--color-border);border-radius:var(--rounded);padding:1.4rem 1.5rem;
  transition:border-color .3s var(--ease)}
.ccard:hover{border-color:var(--color-heading)}
/* Кольцо фокуса рисуется вокруг текста ссылки, а сама ссылка растянута на карточку —
   рамка добавляет второй, более заметный признак. */
.ccard:focus-within{border-color:var(--color-heading)}
.ccard__k{font-size:.7rem;text-transform:uppercase;letter-spacing:.14em;color:var(--muted)}
.ccard__v{font-size:1.15rem;font-weight:700;letter-spacing:-.025em;color:var(--color-heading);
  overflow-wrap:anywhere}
.ccard__v a{color:inherit}
.ccard__v a::after{content:"";position:absolute;inset:0;z-index:1}
.ccard__x{font-size:.85rem;color:var(--muted);line-height:1.55;margin-top:.15rem}
.ccard__go{position:absolute;top:1.15rem;right:1.25rem;color:var(--faint);font-size:.9rem;
  transition:color .3s var(--ease)}
/* единственное появление терракоты на всей странице */
.ccard:hover .ccard__go{color:var(--color-primary)}

/* Снимает лид-подачу .prose>p:first-of-type (1.2rem) именно здесь: правило рассчитано
   на меру 660px, а на контактах читают не прозу, а факты. .prose для статей не трогаем. */
.cnote>p:first-of-type{font-size:inherit;line-height:inherit;color:var(--color-text)}
.cpick__where{margin:.5rem 0 1.25rem;color:var(--muted);font-size:.9rem}

.creq{margin-top:var(--section);border-top:1px solid var(--hair);padding-top:1.5rem;
  font-size:.82rem;color:var(--muted)}
.creq__h{font-size:.7rem;text-transform:uppercase;letter-spacing:.14em;color:var(--muted);
  font-weight:600;margin:0 0 .9rem}
.creq dl{margin:0;display:grid;gap:.4rem 1.5rem;
  /* Ровно две колонки: подпись и значение. Прежний auto-fit давал на обычных ширинах
     нечётное число колонок, и <dt> оказывался над чужим <dd>. */
  grid-template-columns:auto 1fr;align-items:baseline}
.creq dt{color:var(--muted)}
.creq dd{margin:0;color:var(--color-text)}

/* Печать и контрастные режимы для страницы контактов стоят ЗДЕСЬ, а не в общих блоках
   @media print / forced-colors / prefers-contrast наверху файла: те блоки объявлены
   раньше (строки ~477–505), а специфичность у них та же (0,1,0). Дописка туда молча
   проигрывала бы по порядку правилам выше — .cfind осталась бы двухколоночной на бумаге,
   а рамка .ccard перекрасилась бы обратно шорткатом border. Поднимать специфичность
   ради переезда нельзя, поэтому блоки живут после своих базовых правил. */
@media print{
  /* На бумаге «Построить маршрут» не нажимается, а адрес и телефон уже напечатаны выше. */
  .cfind{grid-template-columns:1fr}
  .ccards{grid-template-columns:1fr}
  .contactpage .btn,.cplate__act,.ccard__go{display:none}
  .cfacts dd a{color:#000;text-decoration:none}
  .ccard{break-inside:avoid}
}
@media (forced-colors:active){
  .ccard,.cplate{border:1px solid}
  /* Второй селектор обязателен: .cstatus--open .cstatus__dot выше — это (0,2,0),
     одиночный класс ему проигрывает, и точка «открыто» осталась бы невидимой. */
  .cstatus__dot,.cstatus--open .cstatus__dot{background:CanvasText}
}
@media (prefers-contrast:more){
  .ccard,.cplate{border-color:var(--color-heading)}
}

.notice{border:1px solid var(--color-border);border-radius:var(--rounded);padding:1.75rem;margin-top:2rem;background:var(--color-gray)}

/* ---------- footer ---------- */
.foot{background:var(--color-gray);color:var(--color-text);margin-top:var(--section);
  border-top:1px solid var(--hair);font-size:.88rem}
.foot__in{max-width:var(--wrap);margin:0 auto;padding:clamp(2.5rem,6vw,4.5rem) var(--pad) 3rem;
  display:grid;gap:2.5rem;grid-template-columns:1fr}
@media (max-width:1239px){ .foot__in{padding-inline:var(--pad)} }
.foot__col{min-width:0}
.foot h4{color:var(--color-heading);font-size:.7rem;font-weight:600;text-transform:uppercase;
  letter-spacing:.16em;margin:0 0 1.1rem}
.foot ul{list-style:none;margin:0;padding:0;display:grid;gap:.5rem}
.foot a:hover{color:var(--color-heading)}
/* «Оптовый прайс» в подвале — яркая кнопка (30.09.2026, просьба владельца). Терракота бренда,
   по ней раз в 4 с проходит блик (transform у ::after — без перестройки раскладки), стрелка чуть
   «дышит». .foot .foot__ws (0,2,0) нужен, чтобы .foot a:hover не перекрасил текст в тёмный. */
.foot__wsli{margin-top:.6rem}
.foot .foot__ws{position:relative;overflow:hidden;isolation:isolate;display:inline-flex;align-items:center;
  gap:.55rem;min-height:var(--tap);padding:.6rem 1.15rem;border-radius:var(--btn-radius);
  background:var(--color-primary);color:#fff;font-weight:700;font-size:.78rem;letter-spacing:.08em;
  text-transform:uppercase;text-decoration:none;box-shadow:0 6px 18px -8px rgba(193,74,63,.7);
  transition:background-color .2s,box-shadow .2s,transform .2s}
.foot .foot__ws::after{content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:linear-gradient(100deg,transparent 30%,rgba(255,255,255,.38) 50%,transparent 70%);
  transform:translateX(-120%);animation:footWsShine 4s ease-in-out 1s infinite}
.foot .foot__ws:hover,.foot .foot__ws:focus-visible{color:#fff;background:var(--color-primary-ink);
  box-shadow:0 10px 24px -10px rgba(193,74,63,.85);transform:translateY(-1px)}
.foot__ws-ar{display:inline-block;animation:footWsArrow 2s ease-in-out infinite}
@keyframes footWsShine{0%{transform:translateX(-120%)}35%,100%{transform:translateX(120%)}}
@keyframes footWsArrow{0%,100%{transform:translateX(0)}50%{transform:translateX(3px)}}
@media (prefers-reduced-motion:reduce){
  .foot .foot__ws::after,.foot__ws-ar{animation:none}
  .foot .foot__ws{transition:none}
  .foot .foot__ws:hover{transform:none}
}
@media (forced-colors:active){ .foot .foot__ws{border:1px solid ButtonText} .foot .foot__ws::after{display:none} }
/* Соцсети — круглые иконки (30.09.2026). .foot .foot__social, а не голый класс: иначе
   .foot ul{display:grid} (0,1,1) перебивал бы флекс. Круг 44px — тач-цель сама по себе. */
.foot__icons{display:flex;flex-wrap:wrap;align-items:center;gap:.6rem;margin-top:1rem}
.foot .foot__social{display:flex;gap:.6rem;margin:0}
.soc{display:grid;place-items:center;width:var(--tap);height:var(--tap);border-radius:50%;
  border:1px solid var(--color-border);background:var(--bg);color:var(--color-heading);
  transition:background-color .2s var(--ease),border-color .2s var(--ease),color .2s var(--ease)}
.soc svg{width:1.2rem;height:1.2rem;fill:currentColor}
@media (hover:hover){ .foot .soc:hover{background:var(--color-heading);border-color:var(--color-heading);color:#fff} }
.soc:focus-visible{outline:2px solid var(--color-heading);outline-offset:2px}
/* Язык — та же кнопка, но пилюлей: глобус (контурный, не заливка) + код языка, на который ведёт. */
.soc--lang{display:inline-flex;align-items:center;justify-content:center;gap:.4rem;width:auto;padding-inline:.85rem .95rem;border-radius:999em;
  font-size:.75rem;font-weight:600;letter-spacing:.08em;text-decoration:none}
.soc--lang svg{fill:none;stroke:currentColor;stroke-width:1.6}
@media (forced-colors:active){ .soc{border-color:CanvasText} .soc svg{fill:CanvasText} .soc--lang svg{fill:none;stroke:CanvasText} }
.foot__bar{border-top:1px solid var(--hair)}
.foot__bar-in{max-width:var(--wrap);margin:0 auto;padding-block:1.1rem;padding-inline:var(--pad);
  color:var(--muted);font-size:.76rem;display:flex;flex-wrap:wrap;gap:.6rem 1.5rem;
  justify-content:space-between;align-items:center;text-transform:uppercase;letter-spacing:.06em}
.foot__links{display:flex;flex-wrap:wrap;gap:1.25rem}
/* Реквизиты продавца (ЗоЗПП ст. 9, ПП РФ № 2463) — своей строкой под копирайтом и
   ссылками: .foot__bar-in уже перенослив (flex-wrap:wrap, row-gap .6rem), поэтому
   реквизиты не выталкивают ссылки за край. Капитель и трекинг ряда снимаем — это
   связный текст, а не метка. .8rem, а не .76rem ряда: на телефоне ряд и так
   поднимается до .8rem, ниже юридическую строку опускать нельзя. */
.foot__legal{flex-basis:100%;text-transform:none;letter-spacing:0;line-height:1.5;
  font-size:.8rem;color:var(--muted)}

/* =========================================================================
   Cart · checkout · account · orders
   ========================================================================= */
.cart{display:grid;gap:2.5rem;grid-template-columns:1fr}
.cart__main{display:grid;gap:2.5rem;grid-template-columns:1fr;min-width:0}
.cart__lines{display:flex;flex-direction:column}
.cl{display:grid;grid-template-columns:64px 1fr auto;gap:1.25rem;align-items:center;
  padding:1.4rem 0;border-bottom:1px solid var(--hair);min-width:0}
.cl:first-child{border-top:1px solid var(--hair)}
.cl__media{width:64px;height:64px;border-radius:8px;overflow:hidden;background:var(--color-gray);
  display:grid;place-items:center;color:var(--color-gray-2);font-size:.6rem;flex:none}
.cl__media img{width:100%;height:100%;object-fit:cover}
.cl__body{min-width:0}
.cl__name{font-weight:600;letter-spacing:-.02em;color:var(--color-heading);overflow-wrap:break-word}
.cl__opt{color:var(--muted);font-size:.82rem}
/* Помол и фасовка правятся прямо в строке — иначе смена веса стоит пяти шагов
   (убрать → найти в каталоге → выбрать вес → выбрать помол → добавить). */
.cl__opts{display:flex;align-items:center;gap:.6rem;flex-wrap:wrap;margin-top:.45rem}
/* min-width сбрасываем: .sel--sm держит 11rem, и в узкой колонке (iPad портрет —
   тело строки ~104px) селект вылезал поверх колонки с ценой на 72px. */
.cl__opts .sel--sm{min-width:0;flex:0 1 12rem;max-width:12rem}
.cl__ctl{display:flex;align-items:center;gap:1.25rem;margin-top:.55rem}
/* Высоту задаёт и сама пилюля .qty (3.25rem), и её содержимое. Ужать только детей
   мало: у контейнера оставалась мёртвая зона ~9px сверху и снизу. */
.cl__ctl .qty{height:auto}
.cl__ctl .qty button,.cl__ctl .qty input{height:2.15rem;width:1.9rem}
/* «−» на единице выключена, а не удаляет молча — удаление только явной «Убрать». */
.cl__ctl .qty button[disabled]{opacity:.3;cursor:not-allowed}
.cl__rm{background:none;border:0;color:var(--muted);font-size:.78rem;cursor:pointer;
  text-transform:uppercase;letter-spacing:.08em;padding:0}
.cl__rm:hover{color:var(--color-primary)}
/* Объяснение стоит там, где произошло изменение, а не общей плашкой за экран выше. */
.cl__note{margin:.4rem 0 0;font-size:.75rem;color:var(--color-primary-ink)}
.cl.is-flagged{box-shadow:inset 2px 0 0 var(--color-primary-ink)}
.cl__price{font-weight:600;white-space:nowrap;color:var(--color-heading);
  display:flex;flex-direction:column;align-items:flex-end}
.cl__old{color:var(--muted);font-weight:400;font-size:.82rem;margin-right:.4rem}
.cl__unit{display:block;margin-top:.2rem;font-size:.78rem;color:var(--muted);font-weight:400}
/* Приглушённая цена честно значит «сервер ещё не подтвердил»: в JS не считается ничего. */
.cl.is-pending .cl__price{opacity:.4;transition:opacity .15s var(--ease)}
.cart__side{border:1px solid var(--color-border);border-radius:var(--rounded);
  padding:clamp(1.25rem,2.4vw,1.75rem);
  display:flex;flex-direction:column;gap:.85rem;height:max-content}
.cart__row{display:flex;justify-content:space-between;gap:1rem;font-size:.92rem}
.cart__row--total{font-size:1.2rem;font-weight:700;letter-spacing:-.02em;
  border-top:1px solid var(--hair);padding-top:.9rem;margin-top:.3rem}
/* «Вы экономите» — тихая пометка ПОД итогом, а не строка столбика (A14): старая цена уже
   учтена в «Товарах», и внутри столбика строку вычитали второй раз. border:0 — у
   пометки нет линии, какой бы соседний модификатор её ни дал. */
.cart__row--saved{font-size:.8rem;color:var(--color-secondary);border:0}
/* Тихие ссылки («Продолжить покупки», «К оформлению» без оплаты): тач-цель 44px
   добираем по вертикали — было ~24px. */
.cart__cont{display:flex;align-items:center;justify-content:center;min-height:var(--tap);
  text-align:center;font-size:.85rem;color:var(--muted)}
.cart__empty{text-align:center;padding:4rem 0}
.cart__empty p{color:var(--muted);margin-bottom:1.5rem;font-size:1.05rem}
/* Пока сервер считает — суммы приглушены. Ни одна цифра тут не пересчитывается в JS,
   поэтому приглушённое старое значение и означает «ещё не подтверждено». */
.cart__side.is-pending .cart__row b,.cartbar.is-pending b{opacity:.4;transition:opacity .15s var(--ease)}
/* Липкая полоса с итогом — только на телефоне (правила ниже, в мобильном блоке):
   на десктопе её роль играет липкая .cart__side. */
.cartbar{display:none}
.cartbar__t{font-size:.95rem;color:var(--muted)}
/* JS вешает is-disabled на «К оформлению», когда оформлять нечего. Без правила класс был
   немым: приглушение держалось только на aria-disabled, которого CSS не видит. */
.btn.is-disabled{opacity:.45;pointer-events:none}
.cartbar__t b{color:var(--color-heading);font-weight:700;letter-spacing:-.02em}

/* server-side cart messages ("we adjusted your order", network failure) */
/* Корректировки копятся и держатся до крестика, поэтому блоку нужен и сам крестик,
   и запас прокрутки: JS уводит сюда фокус, а над ним стоит липкая шапка. */
.cartmsg{position:relative;margin-bottom:1.25rem;scroll-margin-top:calc(var(--hdr-h) + 1rem)}
.cartmsg ul{margin:.35rem 0 0;padding-left:1.1rem}
.cartmsg li{margin:.15rem 0}
.cartmsg .btn--sm{margin-top:.6rem}
.cartmsg__x{position:absolute;top:.35rem;right:.5rem;width:2rem;height:2rem;
  background:none;border:0;font-size:1.1rem;line-height:1;cursor:pointer;color:inherit}
/* Оба класса в селекторе не ради краткости: .formmsg задаёт padding шорткатом и стоит
   ниже по файлу, иначе первая строка сообщения уезжает под крестик. */
.formmsg.cartmsg{padding-right:2.6rem}
/* Сетевая ошибка — одна заменяемая строка над списком, она не накапливается. */
.cartmsg__net{display:block;margin-bottom:.5rem;font-weight:600}
.cartmsg:focus{outline:none}
.cartmsg:focus-visible{outline:2px solid var(--color-heading);outline-offset:2px}

/* progress towards free shipping */
.shipbar{margin:.9rem 0 .25rem;font-size:.8rem;color:var(--muted)}
.shipbar__t{display:block;margin-bottom:.4rem}
.shipbar__track{height:3px;border-radius:2px;background:var(--color-gray-2);overflow:hidden}
.shipbar__fill{display:block;height:100%;background:var(--color-heading);width:0;
  transition:width .4s var(--ease)}
.shipbar.is-free .shipbar__fill{background:var(--color-secondary)}
.shipbar.is-free .shipbar__t{color:var(--color-secondary);font-weight:600}

/* loading skeletons — shown until the first server quote lands */
.sk{display:block;border-radius:6px;background:var(--color-gray);position:relative;overflow:hidden;
  color:transparent!important}
.sk--line{height:.9em;width:100%}
.sk--price{height:1em;width:4.5rem}
@media (prefers-reduced-motion:no-preference){
  .sk::after{content:"";position:absolute;inset:0;transform:translateX(-100%);
    background:linear-gradient(90deg,transparent,rgba(255,255,255,.7),transparent);
    animation:skshine 1.2s infinite}
  @keyframes skshine{to{transform:translateX(100%)}}
}

.checkout{display:grid;gap:2.5rem;grid-template-columns:1fr}
.cform{border:0;min-width:0;padding:0;margin:0 0 1.75rem;display:flex;flex-direction:column;gap:.35rem}
.cform legend{padding:0;font-size:.8rem;text-transform:uppercase;letter-spacing:.14em;
  color:var(--muted);font-weight:600;margin-bottom:.5rem}
/* .cform .radio и .cform--fx удалены 30.09.2026 вместе с разметкой: способ получения
   на обеих страницах теперь вкладки .seg (блок ниже). .cform остаётся — на нём формы
   кабинета и входа. */
.radio__price{margin-left:auto;color:var(--muted);font-size:.85rem;white-space:nowrap}
/* Единственный доступный способ печатается строкой: радиокнопка, которую нельзя
   переключить, только притворяется выбором. */
.fxline{display:flex;align-items:center;gap:.6rem;margin:.4rem 0;font-size:.95rem}
.fxline b{font-weight:600;color:var(--color-heading)}
.checkout__pickup{background:var(--color-gray);border-radius:10px;padding:1rem 1.15rem;font-size:.9rem;color:var(--color-text)}
.checkout__pay{color:var(--muted);font-size:.75rem;text-align:center;margin:.85rem 0 0;
  text-transform:uppercase;letter-spacing:.12em}
.checkout__hint,.checkout__req{color:var(--muted);font-size:.8rem;line-height:1.5}
.checkout__consent{color:var(--muted);font-size:.8rem;text-align:center;margin:.85rem 0 0;line-height:1.5}
.checkout__consent a{text-decoration:underline;text-decoration-color:var(--color-border);text-underline-offset:3px}
.checkout__summary{border:1px solid var(--color-border);border-radius:var(--rounded);
  padding:1.75rem;height:max-content}
/* .checkout__lines, .sl, .checkout__edit, .checkout__head и селектор .checkout__soon
   удалены 30.09.2026 вместе с разметкой: список сводки теперь .osum__list (блок ниже),
   «Изменить» — .osum__edit. .checkout__pay живое — его печатает p[data-pay-note]. */

/* =========================================================================
   Корзина и оформление по образцу bushe.ru (30.09.2026)
   Контракт — docs/checkout-redesign-spec-2026-09-30.md (§7, §10 «Z3»). Шапки потока,
   вкладки способа получения, «Остались вопросы?», шаги оформления, окна самовывоза,
   промокод, карточки оплаты, сводка «Ваш заказ».
   Вертикальные отступы — ТОЛЬКО padding-block/margin-block: шорткат `padding: X 0`
   обнулил бы padding-inline у .wrap. :has() не используем — в старых браузерах он
   отваливается молча. Ни один класс здесь не носит .wchip: иначе вмешались бы глобальные
   обработчики клика и стрелок каталога.
   ========================================================================= */

/* Шапка потока: «← Вернуться …» (правило .flowback — в группе .setpage__back) и h1.
   Стоит НИЖЕ .shopflow h1 намеренно: специфичность равная, и иначе победил бы
   .shopflow h1{margin-bottom:.6em}. Кегль — как у заголовка каталога (.shophead h1). */
.flowhead{margin-block:.35rem clamp(1.25rem,3vw,2rem)}
.flowhead h1{margin:0;font-size:clamp(1.75rem,3.5vw,2.5rem);letter-spacing:-.035em}

/* Вкладки «Доставка | Самовывоз» — трек-пилюля. Под каждой вкладкой НАСТОЯЩЕЕ радио
   (name=fulfillment, data-fx) — растянутое на всю вкладку и прозрачное: клик, стрелки
   клавиатуры и скринридер работают как у обычной радиогруппы, store.js читает :checked. */
.seg{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(0,1fr);gap:.25rem;padding:.25rem;
  background:var(--color-gray);border:1px solid var(--hair);border-radius:var(--btn-radius)}
.seg__o{position:relative;display:flex;min-width:0}
/* (0,2,0) обязана перебить глобальное input[type=radio] (0,1,1) с его 1.15rem: радио
   перекрывает вкладку целиком, иначе кликабельным был бы квадратик 18×18 в углу.
   Та же анатомия у карточек оплаты. */
.seg__in[type=radio],.paycard__in[type=radio]{position:absolute;inset:0;width:100%;height:100%;
  margin:0;opacity:0;cursor:pointer;z-index:1}
.seg__face{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.1rem;
  min-height:3rem;padding:.4rem .5rem;border-radius:var(--btn-radius);text-align:center;
  font-size:.875rem;font-weight:600;line-height:1.25;color:var(--color-heading);
  transition:background-color .25s var(--ease),color .25s var(--ease)}
.seg__p{font-size:.72rem;font-weight:500;color:var(--muted);white-space:nowrap;font-variant-numeric:tabular-nums}
.seg__in:checked+.seg__face{background:var(--btn-bg);color:#fff}
.seg__in:checked+.seg__face .seg__p{color:rgba(255,255,255,.75)}
.seg__in:focus-visible+.seg__face{outline:2px solid var(--color-primary);outline-offset:2px}
/* На оформлении трек не тянется на всю колонку формы (~730px на 1280): две «таблетки» по
   360px спорили бы по весу с главной кнопкой. В корзине его и так держит карточка. */
.checkout .seg{max-width:28rem}

/* Подписи колонок «Итог» и «Ваш заказ» — служебный капс, как у .acct__box h2. */
.cart__h,.osum__h{margin:0;font-size:.8rem;text-transform:uppercase;letter-spacing:.14em;color:var(--muted)}
/* Стоит НИЖЕ .cart__row--total: скидка — настоящие деньги, зелёным, как списание Тагл Пэй. */
.cart__row--disc b{color:var(--color-secondary)}
/* Код в корзине перестал действовать. Цвет текста, а не --muted: «код не действует» рядом
   с выросшей суммой не имеет права быть тихим. */
.cart__promo{display:flex;flex-wrap:wrap;align-items:center;gap:.25rem .75rem;margin:0;
  font-size:.85rem;color:var(--color-text)}
.cart__promo-x{min-height:var(--tap);padding:0 .25rem;border:0;background:none;color:inherit;
  text-decoration:underline;cursor:pointer;font:inherit}

/* «Остались вопросы?» — готовые .sect__head и .ccards/.ccard со страницы контактов. */
.cart-help{margin-top:clamp(2.5rem,6vw,4.5rem);min-width:0}
/* Ссылка «Контакты» — тач-цель 44px в высоту (была 62×24). Текст прижат к низу, а не
   по центру: по центру он уезжал на ~10px выше базовой линии h2. padding-bottom .sect__all
   не трогаем — он и держит подпись на одной линии с заголовком. */
.cart-help .sect__all{display:inline-flex;align-items:flex-end;min-height:var(--tap)}

/* Шаги оформления. Номер рисует CSS-счётчик: скрытый шаг ([hidden] = display:none)
   счётчик не увеличивает, поэтому у доставки номера идут 1-2-3-4-5 без дыры. */
.checkout__form{counter-reset:cstep;min-width:0}
/* Подсказка гостю и «* обязательные поля» стоят до шага 1; без своих полей их верхний
   отступ сдвигал форму ниже сводки. */
.checkout__form>.checkout__req,.checkout__form>.checkout__hint{margin-block:0 1rem}
.cstep{counter-increment:cstep;padding-block:clamp(1.5rem,3vw,2rem);display:grid;gap:1rem;min-width:0}
.cstep:first-of-type{padding-top:0}
.cstep~.cstep{border-top:1px solid var(--hair)}
.cstep__h{display:flex;align-items:center;gap:.7rem;margin:0;font-size:clamp(1.1rem,2.2vw,1.35rem)}
.cstep__n{flex:none;width:1.625rem;height:1.625rem;border-radius:50%;display:grid;place-items:center;
  background:var(--color-heading);color:#fff;font-size:.72rem;font-weight:700;letter-spacing:0}
.cstep__n::before{content:counter(cstep)}
.cstep .field{margin-block:0}
.cstep .field__hint{margin-bottom:0}
/* В гриде внешние поля детей не схлопываются и складывались бы с gap:1rem — низ шага 6
   выходил с зазорами ~2rem вместо ритма 1rem. */
.cstep>.consent,.cstep>.depbox,.cstep>.checkout__pay,.cstep>.checkout__terms,.cstep>.checkout__hint,
.cstep>.checkout__due,.cstep>.checkout__fine,.cstep>.cartmsg{margin-block:0}
/* Заметка об оплате и оферта читаются одним блоком под кнопкой. */
.checkout__fine{display:grid;gap:.4rem}
.checkout__fine>*{margin:0}

/* Имя и телефон в строку — только когда обоим хватает 14rem; иначе столбиком. */
.frow{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(min(100%,14rem),1fr))}
.frow>.field{min-width:0}

/* Карточка самовывоза: адрес и часы из тех же чисел, что окна. Никаких iframe карт. */
.pickcard{display:grid;gap:.45rem;justify-items:start;padding:1.15rem 1.25rem;
  background:var(--color-gray);border:1px solid var(--hair);border-radius:var(--rounded)}
.pickcard__k{margin:0;font-size:.7rem;text-transform:uppercase;letter-spacing:.14em;color:var(--muted)}
.pickcard__addr{margin:0;max-width:100%;font-size:1.1rem;font-weight:700;line-height:1.3;
  letter-spacing:-.02em;color:var(--color-heading);overflow-wrap:break-word}

/* Окна самовывоза: две колонки только при форме шире ~39rem — иначе подпись ночного окна
   в закрытом select обрезалась бы (Chrome режет без многоточия). */
.slots{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(min(100%,19rem),1fr))}
.slots>.field{min-width:0}
.field--err .sel{border-color:var(--color-primary-ink)}

/* Галочки «позвонить» и «меньше упаковки» — та же анатомия, что у .depbox (селекторы
   дописаны в его группы ниже по файлу), здесь только ритм списка. */
.opts{display:grid;gap:.15rem}
.opts>.opt{margin-block:0}

/* Промокод. Видимое поле без name: в POST уходит только применённый код (скрытое поле).
   minmax(0,1fr) у трека и min-width:0 у ряда обязательны: иначе минимум ряда — это
   собственная ширина поля (size=20, ~211px) + кнопка, auto-трек растягивался до 350px, и на
   360px у страницы появлялась горизонтальная прокрутка, а на 375 ряд вылезал за колонку. */
.promo{display:grid;grid-template-columns:minmax(0,1fr);gap:.5rem;min-width:0}
.promo__row{display:flex;gap:.5rem;min-width:0}
/* (0,1,1) — наравне с глобальным input[type=text], выигрывает порядком. */
.promo__row input{flex:1 1 auto;min-width:0;width:auto;text-transform:uppercase;letter-spacing:.06em}
.promo__row input::placeholder{text-transform:none;letter-spacing:0}
/* высота — от .btn (3rem = --input-height): поле и кнопка одной линией */
.promo__go{flex:none;padding-inline:1.5rem}
.promo__ok{display:flex;align-items:center;flex-wrap:wrap;gap:.5rem;margin:0;min-height:var(--tap);
  padding:.25rem .25rem .25rem 1.1rem;border-radius:var(--btn-radius);
  background:var(--color-secondary-soft);color:var(--color-secondary);font-size:.9rem}
/* «Применён, но не действует» (корзина уменьшилась): код остаётся с «×», причина — тоном
   ошибки. */
.promo__ok.is-bad{background:var(--color-gray);color:var(--color-text)}
.promo__ok.is-bad .promo__note{color:var(--color-primary-ink)}
.promo__tag{flex:none;font-weight:700;letter-spacing:.08em}
/* Нулевая основа: длинная причина («действует на заказ от 1 500 ₽») переносится ВНУТРИ
   своей колонки, а код и «×» остаются в одной строке с ней. С основой по содержимому
   на 390px всё рассыпалось на три строки, и пилюля раздувалась до 118px. */
.promo__note{flex:1 1 0;min-width:0}
.promo__x{margin-left:auto;width:var(--tap);height:var(--tap);border:0;border-radius:50%;
  background:none;color:inherit;font-size:1.2rem;line-height:1;cursor:pointer}
/* На телефоне код бывает длинным (до 32 знаков), и колонка пояснения рядом с ним сжималась
   до слова в строку: «Промокод / CLAUDETEST7F3K / применён: / −10%» — четыре строки в пилюле.
   Там код встаёт первой строкой, пояснение — второй, «×» — справа на обе; скругление обычное,
   пилюля на две строки читается кляксой. */
@media (max-width:47.999rem){
  .promo__ok{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:.1rem .5rem;
    padding:.6rem .25rem .6rem 1.1rem;border-radius:var(--rounded)}
  .promo__tag{grid-column:1;overflow-wrap:anywhere}
  .promo__note{grid-column:1}
  .promo__x{grid-column:2;grid-row:1 / span 2;margin-left:0}
}
.promo__msg{margin:0;font-size:.85rem;color:var(--muted)}
.promo.field--err .promo__msg{color:var(--color-primary-ink)}
.promo.field--err .promo__in{border-color:var(--color-primary-ink)}
/* Пустой <p> с margin:0 и так нулевой высоты — отрицательное поле лишь снимает лишний шаг
   gap. НЕ display:none: текст вставляется той же мутацией, что делает узел видимым, а
   изменение внутри display:none VoiceOver/Safari и часть NVDA не объявляют. */
.promo__msg:empty{margin-top:-.5rem}

/* Карточки оплаты «Картой онлайн | СБП». На телефоне и планшете (колонка формы ~384px
   на 768) — столбиком во всю ширину, две в ряд — от ~430px контента: при 9.5rem на
   375px текст ломался в столбики мелких слов. Радио растянуто на карточку (правило
   выше, вместе с .seg__in). */
.paycards{display:grid;gap:.6rem;grid-template-columns:repeat(auto-fit,minmax(min(100%,13rem),1fr))}
.paycard{position:relative;display:flex;min-width:0}
.paycard__face{flex:1;display:grid;grid-template-columns:auto minmax(0,1fr);align-content:center;
  gap:.15rem .7rem;position:relative;min-height:4rem;padding:.85rem 2.2rem .85rem 1rem;
  border:1px solid var(--color-border);border-radius:var(--rounded);background:var(--color-background);
  line-height:1.35;transition:border-color .25s var(--ease)}
.paycard__ic{width:1.5rem;height:1.5rem;grid-row:1/span 2;align-self:center;color:var(--color-heading)}
.paycard__t{font-size:.92rem;font-weight:600;color:var(--color-heading)}
.paycard__x{grid-column:2;font-size:.78rem;color:var(--muted)}
/* Кружок — --faint, не --color-border: #e4e1db на белом ≈1.3:1, индикатор состояния не
   виден (WCAG 1.4.11); --faint — проектный тон для нетекстовых элементов, ~3.2:1. */
.paycard__dot{position:absolute;top:.85rem;right:.85rem;width:1.125rem;height:1.125rem;
  border-radius:50%;border:1.5px solid var(--faint);background:var(--color-background)}
/* Выбор передаётся ФОРМОЙ (заполненный кружок), а не только цветом рамки. Тень .5px
   добавляет толщину рамке без сдвига раскладки. */
.paycard__in:checked+.paycard__face{border-color:var(--color-heading);
  box-shadow:inset 0 0 0 .5px var(--color-heading)}
.paycard__in:checked+.paycard__face .paycard__dot{border:5px solid var(--color-heading)}
.paycard__in:focus-visible+.paycard__face{outline:2px solid var(--color-heading);outline-offset:2px}
.paycard__in:disabled+.paycard__face{opacity:.5}
@media (hover:hover){
  .paycard__in:not(:disabled):hover+.paycard__face{border-color:var(--color-heading)}
}

/* Сводка «Ваш заказ». На телефоне свёрнута в полосу .osum__bar (details), на десктопе полоса
   скрыта и details всегда открыт. */
.osum__head{display:flex;align-items:center;justify-content:space-between;gap:1rem;margin-bottom:.6rem}
/* тихий подчёркнутый капс, не терракота: терракота на странице — только у ошибок */
.osum__edit{display:inline-flex;align-items:center;min-height:var(--tap);font-size:.72rem;font-weight:600;
  text-transform:uppercase;letter-spacing:.12em;color:var(--muted);text-decoration:underline;
  text-decoration-color:var(--color-border);text-underline-offset:4px}
.osum__edit:hover{color:var(--color-heading)}
.osum__scroll{margin-bottom:1rem}
.osum__list{list-style:none;margin:0;padding:0}
.osum__i{display:grid;grid-template-columns:48px minmax(0,1fr) auto;gap:.8rem;align-items:start;
  padding-block:.75rem;border-bottom:1px solid var(--hair)}
.osum__i:first-child{border-top:1px solid var(--hair)}
.osum__media{width:48px;height:48px;border-radius:8px;overflow:hidden;background:var(--color-gray);
  display:grid;place-items:center;color:var(--color-gray-2);font-size:.55rem}
.osum__media img{width:100%;height:100%;object-fit:cover}
.osum__ph{font-weight:700;letter-spacing:.1em}
.osum__body{display:flex;flex-direction:column;gap:.15rem;min-width:0}
.osum__name{font-size:.9rem;font-weight:600;line-height:1.35;color:var(--color-heading);overflow-wrap:break-word}
.osum__meta{font-size:.78rem;line-height:1.4;color:var(--muted)}
.osum__sum{font-size:.9rem;font-weight:600;white-space:nowrap;font-variant-numeric:tabular-nums;
  color:var(--color-heading)}
.osum__bar{list-style:none;cursor:pointer;display:flex;justify-content:space-between;align-items:center;
  gap:1rem;min-height:3.25rem;padding:.6rem 1.1rem;font-weight:600;color:var(--color-heading);
  -webkit-tap-highlight-color:transparent}
.osum__bar::-webkit-details-marker{display:none}
.osum__bar-t{margin-right:auto}
.osum__bar b{white-space:nowrap;font-variant-numeric:tabular-nums}
/* шеврон — копия .facets__btn::after */
.osum__bar::after{content:"";flex:none;width:.4rem;height:.4rem;border-right:1.4px solid currentColor;
  border-bottom:1.4px solid currentColor;transform:rotate(45deg) translate(-1px,-1px);
  transition:transform .25s var(--ease)}
.osum__fold[open]>.osum__bar::after{transform:rotate(225deg) translate(-1px,-1px)}
@media (min-width:48rem){
  .osum__bar{display:none}
  /* Своя прокрутка списка — только на десктопе. На телефоне список раскрывается целиком
     внутри свёрнутого details: вложенная прокрутка с overscroll-behavior:contain
     «застревала» бы под пальцем. */
  .osum__scroll{max-height:calc(var(--sv) * .4);overflow-y:auto;overscroll-behavior:contain}
}
@media (max-width:47.999rem){
  /* «Ваш заказ» уже сказано в полосе summary — второй видимый заголовок был бы повтором.
     h2 остаётся для скринридера, «Изменить» прижата вправо. */
  .osum__h{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;
    clip:rect(0 0 0 0);white-space:nowrap}
  .osum__head{justify-content:flex-end}
}

/* Спецрежимы блока. Стоят ПОСЛЕ своих базовых правил: общие блоки выше по файлу при равной
   специфичности проиграли бы им порядком (та же причина, что у контактов). */
@media (forced-colors:active){
  .seg{border:1px solid}
  .seg__in:checked+.seg__face{forced-color-adjust:none;background:Highlight;color:HighlightText}
  .seg__in:checked+.seg__face .seg__p{forced-color-adjust:none;color:HighlightText}
  .pickcard,.paycard__face{border:1px solid}
  .paycard__in:checked+.paycard__face .paycard__dot{forced-color-adjust:none;border-color:Highlight}
  .cstep__n{forced-color-adjust:none;background:CanvasText;color:Canvas}
}
@media (prefers-contrast:more){
  .seg,.pickcard,.paycard__face{border-color:var(--color-heading)}
  .paycard__dot{border-color:var(--color-heading)}
}
@media print{
  /* a.flowback (0,1,1), а не .flowback: базовое правило .flowback живёт НИЖЕ по файлу (в
     группе .setpage__back) и при равной специфичности вернуло бы ссылку на бумагу. */
  .osum__bar,a.flowback{display:none}
}

/* ---------- личный кабинет ---------- */
/* Своя обёртка, а НЕ .doc: .doc — это читательская мера 660px для статей и страниц, и в
   ней кабинет на экране 1440px занимал 632px, а колонка суммы в истории депозита
   сжималась до 35px, разрывая «−15 505 ₽» на три строки. Ровно этот промах уже чинили
   в корзине (.shopflow). Нижний padding-block здесь 0: воздух до подвала дают .main и
   .foot (110 + 110), а третий такой же слой давал 350px пустоты перед подвалом.
   Только padding-block: шорткат обнулил бы padding-inline у .wrap. */
.acctpage{padding-block:clamp(1.5rem,4vw,3rem) 0}

/* Шапка кабинета: фото — имя — «Выйти». Служебное слово «Аккаунт» стоит НАД именем и
   намеренно мельче его (тот же приём, что на Контактах): человек пришёл не читать слово
   «Аккаунт». Фотография занимает место прежнего заголовочного ряда, а не добавляется
   к нему. flex-wrap обязателен: на узком экране «Выйти» уезжает на свою строку. */
.acct__head{display:flex;align-items:center;flex-wrap:wrap;gap:clamp(1rem,3vw,1.5rem);
  padding-bottom:1.5rem;border-bottom:1px solid var(--hair);margin-bottom:2rem}
/* Внутри — тоже flex с переносом: подпись, имя и подсказка занимают всю строку
   (flex-basis:100%), а две формы (загрузить / удалить) встают рядом друг с другом.
   Обёртка ради этого не нужна — и не должна появляться: формы обязаны быть отдельными. */
.acct__id{display:flex;flex-wrap:wrap;align-items:center;gap:.55rem .6rem;flex:1 1 16rem;min-width:0}
.acct__eyebrow,.acct__name,.avaform__hint{flex-basis:100%;margin:0}
.acct__eyebrow{font-size:.7rem;font-weight:600;text-transform:uppercase;letter-spacing:.16em;
  color:var(--muted);line-height:1.3}
.acct__name{font-size:clamp(1.6rem,3.6vw,2.4rem);font-weight:800;line-height:1.06;
  letter-spacing:-.035em;color:var(--color-heading);overflow-wrap:anywhere}
/* «Настройки» и «Выйти» — тихие текстовые переходы, а не пилюли: это служебная
   навигация, а не главное действие страницы. Тач-цель добираем ТОЛЬКО по вертикали
   (min-height): min-width здесь уже однажды съел горизонталь и обрезал надпись
   в шапке сайта. Набраны они одним правилом намеренно — стоят рядом, и разный вид
   читался бы как разная важность.
   «Настройки» — единственный видимый путь к профилю и паролю после их переезда
   на /account/settings/, поэтому ссылка живёт в шапке, а не в подвале страницы. */
/* Пара — одной обёрткой. Отдельными элементами шапки они разъезжались
   при переносе строки: у формы выхода margin-left:auto, и на узком экране ссылка
   осталась бы у левого края, а «Выйти» — у правого. Внутри обёртки свободного места
   нет (flex:none по содержимому), поэтому это auto тут обнуляем явно — специфичность
   (0,2,0) против (0,1,0) ниже по файлу, порядок правил роли не играет. */
.acct__nav{display:flex;align-items:center;flex-wrap:wrap;gap:.9rem;margin-left:auto;flex:none}
.acct__nav .acct__signout{margin-left:0}
.acct__signout{margin-left:auto;flex:none}
/* «Настройки» и «Выйти» — РАЗНЫЕ по весу, и это не украшательство. Настройки —
   единственный путь к профилю и паролю, то есть вход в раздел; выход — служебное
   действие, которое жмут раз в жизни. Одинаковые подчёркнутые капсы рядом читались
   как две половины одного, и человек не находил, где сменить пароль.
   Обводка против тихого текста разводит их надёжнее любого отступа. */
.acct__navlink{display:inline-flex;align-items:center;justify-content:center;
  min-height:var(--tap);padding:0 1.1rem;border:1px solid var(--color-border);
  border-radius:var(--btn-radius);background:none;cursor:pointer;color:var(--color-heading);
  font-size:.75rem;text-transform:uppercase;letter-spacing:.12em;font-weight:600;
  text-decoration:none;transition:border-color .3s var(--ease),background .3s var(--ease)}
.acct__navlink:hover{border-color:var(--color-heading);background:var(--color-gray)}
.acct__signout button{display:inline-flex;align-items:center;min-height:var(--tap);
  padding:0;border:0;background:none;cursor:pointer;color:var(--muted);
  font-size:.75rem;text-transform:uppercase;letter-spacing:.12em;font-weight:600;
  text-decoration:underline;text-decoration-color:var(--color-border);text-underline-offset:5px;
  transition:color .3s var(--ease)}
.acct__signout button:hover{color:var(--color-heading)}

/* Фотография профиля. Круг с overflow:hidden — единственное место, где картинка
   кадрируется на витрине; квадрат из неё делает уже Image::ingest, поэтому object-fit
   здесь страхует только чужую (яндексовскую) картинку неквадратных пропорций. */
.ava{display:inline-flex;align-items:center;justify-content:center;flex:none;
  width:2.5rem;height:2.5rem;border-radius:50%;overflow:hidden;background:var(--color-gray)}
.ava--lg{width:72px;height:72px}
.ava__img{width:100%;height:100%;object-fit:cover;border-radius:50%}
/* Без фото — не дыра в вёрстке, а инициалы на фирменном тёмном круге. Такими останутся
   большинство карточек: аватар ставят единицы. */
.ava__ph{display:flex;align-items:center;justify-content:center;width:100%;height:100%;
  background:var(--color-heading);color:#fff;font-weight:800;line-height:1;
  letter-spacing:-.02em;font-size:.95rem;text-transform:uppercase}
.ava--lg .ava__ph{font-size:1.65rem}

/* Формы фотографии. Нативный input[type=file] в этом файле не стилизован вовсе (перечень
   типов в «form controls» его не включает) и отрисовался бы системной кнопкой чужим
   шрифтом посреди страницы на Mont. Поэтому он спрятан в .vh внутри <label class="btn">,
   а видимый фокус с клавиатуры возвращает :focus-within — без него кнопка выбора файла
   была бы недостижима глазами при табуляции. */
.avaform{display:inline-flex;align-items:center;gap:.5rem;margin:0}
.avaform__pick{cursor:pointer}
.avaform__pick:focus-within{outline:2px solid var(--color-heading);outline-offset:2px}
/* Есть JS — файл отправляется сразу по выбору, и отдельная кнопка «Сохранить фото»
   становится лишней. Атрибут ставит store.js; без JS кнопка на месте и форма работает. */
.avaform[data-js-auto] .avaform__save{display:none}
.avaform__del{display:inline-flex;align-items:center;min-height:var(--tap);
  padding:0;border:0;background:none;cursor:pointer;color:var(--muted);font-size:.78rem;
  text-decoration:underline;text-decoration-color:var(--color-border);text-underline-offset:4px;
  transition:color .3s var(--ease)}
.avaform__del:hover{color:var(--color-primary)}
.avaform__hint{font-size:.78rem;color:var(--muted);line-height:1.5}

/* Главная колонка — заказы, рельс — депозит (профиль и пароль переехали на страницу
   настроек). align-items:start обязателен: иначе карточка рельса растягивалась бы до
   высоты истории заказов, а в сетке появлялась бы пустая ячейка. */
.acct{display:grid;gap:1.75rem;grid-template-columns:1fr;align-items:start;margin:0 0 2.5rem}
.acct__main{min-width:0}
/* Рельс печатается ТОЛЬКО когда депозиту есть что сказать — пустого .acct__side в
   разметке не бывает (см. .acct--solo в медиазапросе от 64rem). */
.acct__side{display:grid;gap:1.25rem;align-content:start;min-width:0}

/* ---------- /account/settings/ — профиль и пароль ----------
   Страница состоит из двух форм, поэтому колонка узкая: растянутые на всю --wrap
   (1200px на десктопе) поля ввода никто не набирает, а в кабинете они и жили в рельсе
   шириной 352px. 42rem — вдвое шире прежнего рельса и заметно уже читательских 660px
   у .doc: это форма, а не текст.
   margin-inline:auto ставится ОТДЕЛЬНО от margin-block — шорткат margin обнулил бы
   центрирование (та же ловушка, что со строкой статистики в редакторе статей). */
.setpage{display:grid;gap:1.25rem;max-width:42rem;margin-inline:auto;margin-block:0 2.5rem}
/* Шапка страницы повторяет ритм шапки кабинета: тонкая линия и тот же отступ под ней. */
.setpage__head{display:grid;gap:.35rem;padding-bottom:1.5rem;border-bottom:1px solid var(--hair)}
.setpage__h1{font-size:clamp(1.6rem,3.6vw,2.4rem);font-weight:800;line-height:1.06;
  letter-spacing:-.035em;color:var(--color-heading);margin:0}
/* Дорога назад — ПЕРВЫМ элементом страницы, до заголовка: человек пришёл из кабинета.
   justify-self:start обязателен: элемент грида по умолчанию растягивается на всю
   колонку, и кликабельной стала бы вся строка в 42rem шириной.
   Стрелку рисует CSS, а не разметка: в тексте ссылки она была бы лишним символом
   для скринридера, который прочитает её как «стрелка влево».
   Подчёркивания здесь нет намеренно (в отличие от тихих ссылок в шапке кабинета):
   affordance даёт стрелка, а линия прошла бы и под ней.
   .flowback — та же дорога назад над корзиной («в каталог») и оформлением («в корзину»). */
.setpage__back,.flowback{display:inline-flex;align-items:center;gap:.45rem;justify-self:start;
  min-height:var(--tap);color:var(--muted);font-size:.75rem;font-weight:600;
  text-transform:uppercase;letter-spacing:.12em;text-decoration:none;
  transition:color .3s var(--ease)}
.setpage__back::before,.flowback::before{content:"\2190";font-size:1rem;line-height:1}
.setpage__back:hover,.flowback:hover{color:var(--color-heading)}
.acct__box{border:1px solid var(--color-border);border-radius:var(--rounded);padding:1.75rem;min-width:0}
.acct__box h2{font-size:.8rem;text-transform:uppercase;letter-spacing:.14em;color:var(--muted);margin-bottom:1.1rem}
/* Сворачиваемый блок: хвост истории депозита и карточка смены пароля. Нативный
   <details> — работает без единой строки JS. Подпись сделана под вид .acct__box h2,
   потому что в карточке пароля она этот заголовок и заменяет. */
.acct__more{margin:0}
.acct__more>summary{list-style:none;cursor:pointer;display:inline-flex;align-items:center;gap:.45rem;
  min-height:var(--tap);font-size:.8rem;font-weight:500;text-transform:uppercase;
  letter-spacing:.14em;color:var(--muted);transition:color .3s var(--ease)}
.acct__more>summary::-webkit-details-marker{display:none}
.acct__more>summary::after{content:"";width:.4rem;height:.4rem;flex:none;
  border-right:1.4px solid currentColor;border-bottom:1.4px solid currentColor;
  transform:rotate(45deg) translate(-1px,-1px);transition:transform .25s var(--ease)}
.acct__more[open]>summary::after{transform:rotate(225deg) translate(-1px,-1px)}
.acct__more>summary:hover{color:var(--color-heading)}
.acct__more[open]>summary{margin-bottom:.9rem}
.acct__why{margin:0;padding:0;list-style:none;display:grid;gap:.7rem;font-size:.9rem;color:var(--color-text)}
.acct__why li{display:flex;gap:.6rem;line-height:1.5}
.acct__why li::before{content:"—";color:var(--muted);flex:none}
/* Депозит в кабинете. Кошелёк один на все сайты Toggle: показываем остаток и
   пять последних операций — начисляет и списывает кофейня, здесь только чтение. */
.acct__balance{margin:0 0 .35rem;font-size:1.9rem;line-height:1.1;font-weight:600;
  font-variant-numeric:tabular-nums}
.acct__txns{list-style:none;margin:1rem 0 0;padding:.75rem 0 0;border-top:1px solid var(--color-border)}
.acct__txns li{display:flex;justify-content:space-between;gap:1rem;padding:.28rem 0;font-size:.9rem}
.acct__txns li span{color:var(--muted)}
.acct__orders-h{margin:0 0 .35rem;font-size:.8rem;text-transform:uppercase;letter-spacing:.14em;color:var(--muted)}
.auth{display:grid;gap:3rem;grid-template-columns:1fr}
.auth__col{min-width:0}
.auth__col h1,.auth__col h2{font-size:1.4rem;text-transform:none;letter-spacing:var(--lsp-head);margin-bottom:1.25rem}
.auth__yandex{margin-top:.85rem;width:100%}
.auth__hint{font-size:.85rem;color:var(--muted);margin-top:1.1rem;line-height:1.55}
/* Согласие на обработку персональных данных: три точки сбора (регистрация,
   оформление заказа, вход через Яндекс) — один класс.
   flex-wrap обязателен: без него flex-basis:100% у подписи ошибки не переносит её
   на свою строку, а лишь сжимает текст согласия в узкую колонку.
   .85rem — минимум для юридической подписи, на телефоне НЕ мельчим. */
.consent{display:flex;flex-wrap:wrap;align-items:flex-start;gap:.6rem;font-size:.85rem;
  color:var(--color-text);line-height:1.5;margin:1.1rem 0;
  min-height:var(--tap);padding-block:.35rem}
/* Размер и flex:none чекбоксу уже даёт общее правило (см. «form controls»); здесь
   только вертикальная подгонка к первой строке текста. Проверено: глобальный
   padding:.55rem 1.25rem задан списком типов (search/text/email/password/tel/number/url)
   и на checkbox не течёт — сбрасывать нечего. */
.consent input[type=checkbox]{width:1.15rem;height:1.15rem;flex:none;margin-block:.15rem}
/* Подписи нужна своя основа, как у .depbox__text/.opt__t: без неё flex-wrap отправлял
   весь длинный текст согласия СТРОКОЙ НИЖЕ галочки — галочка висела одна, текст под ней.
   Перенос остаётся только для ошибки (.field__err, flex-basis:100%). */
.consent>span:not(.field__err){flex:1 1 12rem;min-width:0}
.consent a{text-decoration:underline;text-decoration-color:var(--color-border);text-underline-offset:3px}
/* .field--err красит рамку, но у нативного чекбокса рамки нет — ошибка была бы
   невидимой. Кольцо тем же тоном, что и подпись ошибки. */
.consent.field--err input[type=checkbox]{outline:2px solid var(--color-primary-ink);outline-offset:2px}
.consent .field__err{flex-basis:100%;margin-top:.15rem}

.otable{width:100%;border-collapse:collapse;font-size:.9rem;margin:1.25rem 0}
.otable th,.otable td{text-align:left;padding:.75rem .5rem;border-bottom:1px solid var(--hair);vertical-align:top}
.otable thead th{color:var(--muted);font-weight:500;font-size:.7rem;text-transform:uppercase;letter-spacing:.1em}
.otable__total td{font-weight:700;border-top:1px solid var(--color-heading);border-bottom:0}
.otable tfoot td{border-bottom:0;color:var(--muted)}
.otable__items{color:var(--muted);font-size:.82rem;line-height:1.5}
/* Таблица заказов в кабинете: четыре колонки (номер · дата · статус · итого).
   Пятой, «Подробнее →», больше нет — номер уже ссылка на тот же адрес. */
.otable--acct td,.otable--acct th{font-variant-numeric:tabular-nums}
.otable--acct th:last-child,.otable--acct td:last-child{text-align:right;white-space:nowrap}
.order__status{margin:.6rem 0 2rem;text-transform:uppercase;letter-spacing:.08em;font-size:.82rem;color:var(--muted)}
.order__meta{color:var(--color-text);font-size:.9rem;margin:2rem 0}
.order__meta p{margin:.4rem 0}
.order__pay{color:var(--muted);text-transform:uppercase;letter-spacing:.1em;font-size:.75rem}
.order__link{overflow-wrap:anywhere;font-size:.85rem;color:var(--muted)}
.order__keep,.order__help{color:var(--muted);font-size:.85rem;line-height:1.6}
.ostatus{display:inline-block;font-size:.66rem;font-weight:600;text-transform:uppercase;
  letter-spacing:.1em;padding:.2rem .6rem;border-radius:999em;border:1px solid var(--color-border);
  background:transparent;color:var(--color-heading)}
.ostatus--new{border-color:var(--color-primary);color:var(--color-primary)}
.ostatus--processing{border-color:#b6b0a6;color:#6f6960}
.ostatus--shipped{border-color:var(--color-secondary);color:var(--color-secondary)}
.ostatus--done{border-color:var(--color-border);color:var(--muted)}
.ostatus--canceled{border-color:var(--color-border);color:var(--muted);text-decoration:line-through}

/* Платёжная плашка на странице заказа (order_status.php). Пять состояний:
   --wait (ждём оплаты, есть кнопка), --processing (банк подтверждает — кнопки НЕТ
   намеренно, иначе платят второй раз), --fail, --paid, --refunded.
   Цвет здесь только оттенок: смысл каждого состояния написан словами в .paybox__title,
   поэтому плашка остаётся понятной и при forced-colors, и без различения цветов. */
.paybox{border:1px solid var(--color-border);border-left:3px solid var(--color-border);
  border-radius:var(--rounded);background:var(--color-background);
  padding:1.15rem 1.25rem;margin:0 0 1.75rem;
  display:flex;flex-direction:column;gap:.6rem}
.paybox__title{margin:0;font-size:1rem;font-weight:700;letter-spacing:var(--lsp-head);
  color:var(--color-heading)}
.paybox__note{margin:0;color:var(--muted);font-size:.85rem;line-height:1.6;max-width:52ch}
/* Кнопка — существующий .btn.btn--lg (высота 3.25rem = 52px, тач-цель по вертикали
   выполнена без добора; горизонталь не трогаем — грабли §4h). .btn--lg тянется на всю
   ширину, в узкой карточке это выглядит плитой, поэтому только здесь ставим потолок. */
.paybox form{margin:0}
.paybox .btn{max-width:22rem}

.paybox--wait{border-left-color:var(--color-heading);background:var(--color-gray)}
/* Ожидание ответа банка — состояние без действия: приглушаем, чтобы человек
   не искал глазами кнопку, которой тут нет. */
.paybox--processing{border-left-color:var(--faint);background:var(--color-gray)}
.paybox--processing .paybox__title{color:var(--color-text)}
.paybox--fail{border-left-color:var(--color-primary);background:var(--color-primary-soft)}
.paybox--fail .paybox__title{color:var(--color-primary-ink)}
.paybox--paid{border-left-color:var(--color-secondary);background:var(--color-secondary-soft)}
.paybox--paid .paybox__title{color:var(--color-secondary)}
.paybox--refunded{border-left-color:var(--color-border);background:var(--color-gray)}
/* Отменённый заказ. Приглушённая плашка без единого действия: платить по нему нечего,
   а прежде шаблон печатал рядом с «Отменён» живую кнопку «Оплатить». */
.paybox--canceled{border-left-color:var(--color-border);background:var(--color-gray)}
.paybox--canceled .paybox__title{color:var(--color-text)}
/* Отмена — вторичное действие рядом с оплатой, поэтому обводка и малый размер, а не
   заливка: главное действие на этой плашке одно, и это «Оплатить». */
.paybox__cancel{margin:0}
.paybox__cancel .btn{max-width:22rem}
/* Вторая строка внутри плашки: дата возврата, номер операции — служебное уточнение
   к основному тексту, поэтому мельче и тише его. */
.paybox__meta{margin:0;color:var(--muted);font-size:.78rem;line-height:1.5}

/* ---------- отмена заказа покупателем ----------
   Граница отмены — стадия ЗАКАЗА (настройка cancel_until), а не факт оплаты: деньги
   возвращаются автоматически. Поэтому форма отмены живёт и на оплаченном заказе, и в
   истории кабинета, и выглядит одинаково в обоих местах. */
.canform{display:flex;flex-wrap:wrap;align-items:center;gap:.6rem .9rem;margin:0}
/* Галочка и её подпись — одна тач-цель во всю строку метки, как у .consent и .depbox.
   flex-wrap на родителе обязателен: без него длинная подпись сжимала бы кнопку. */
.canform__restore{display:flex;align-items:center;gap:.5rem;min-height:var(--tap);
  font-size:.85rem;color:var(--color-text);line-height:1.4;cursor:pointer}
/* Общее правило полей задаёт инпутам padding списком типов; на checkbox оно не течёт,
   поэтому сбрасывать нечего — только выравниваем размер по строке подписи. */
.canform__restore input[type=checkbox]{width:1.05rem;height:1.05rem;flex:none}
.canform .btn{flex:none}
/* В истории заказов форма живёт СВОЕЙ строкой во всю ширину таблицы (colspan), а
   мера в 20rem не даёт ей уехать за край экрана. Так и найдено замером на 375px:
   таблица из пяти колонок сама шире экрана (557px против 335px видимых), первой ячейке
   достаётся 118px — кнопка в 147px вываливалась из неё поверх соседней колонки, а без
   меры кнопка уезжала на x = 242…389, то есть за правый край. С мерой: подпись 25…230,
   кнопка 25…172, горизонтальной прокрутки у страницы нет. */
.canform--row{gap:.5rem .75rem}
/* Заказ и его отмена — одна запись для человека, поэтому линия между ними снимается,
   а рисуется под парой. Иначе отмена читалась бы как отдельный заказ без номера. */
.otable__lead td{border-bottom:0}
.otable__cancelrow td{padding-top:0;padding-bottom:.9rem}

/* Почему кнопки отмены нет. Четыре разные причины, и каждая — обычный абзац рядом с
   плашкой оплаты, а не тревога: человек ни в чём не виноват. */
.cancel-why{margin:0 0 1.5rem;color:var(--muted);font-size:.85rem;line-height:1.6;max-width:52ch}
.cancel-why a{color:var(--color-heading);text-decoration:underline;
  text-decoration-color:var(--color-border);text-underline-offset:3px}

/* Итог возврата товаров в корзину. Печатает store.js ПОСЛЕ фактического слияния —
   сервер про корзину не обещает ничего, поэтому блок приезжает пустым и скрытым. */
.cartback{border:1px solid var(--color-border);border-left:3px solid var(--color-secondary);
  border-radius:var(--rounded);padding:.9rem 1.15rem;margin:0 0 1.5rem;background:var(--color-gray)}
.cartback__text{margin:0;font-size:.9rem;line-height:1.55;color:var(--color-heading)}
.cartback__list{margin:.5rem 0 0;padding-left:1.1rem;color:var(--muted);
  font-size:.82rem;line-height:1.55}
.cartback__list:empty{display:none}

/* Состояние ОПЛАТЫ в истории заказов кабинета — рядом со статусом сборки (.ostatus),
   но другой формой: заливка против обводки, иначе два чипа подряд читаются как один
   и человек не различает «Новый» и «Ожидает оплаты». */
.pstatus{display:inline-block;white-space:nowrap;font-size:.66rem;font-weight:600;
  letter-spacing:.08em;text-transform:uppercase;padding:.2rem .55rem;border-radius:999em;
  background:var(--color-gray);color:var(--muted)}
.pstatus--paid{background:var(--color-secondary-soft);color:var(--color-secondary)}
.pstatus--fail{background:var(--color-primary-soft);color:var(--color-primary-ink)}
.pstatus--wait{background:var(--color-gray);color:var(--color-text)}
.pstatus--off{background:var(--color-gray);color:var(--muted)}

/* ---------- депозит ----------
   Кошелёк один на все сайты Toggle. В магазине им можно оплатить заказ ЦЕЛИКОМ или
   не оплачивать вовсе: доплатить остаток картой негде, кассы у магазина нет. */

/* Строка списания в сводке. Стоит НИЖЕ .cart__row--total по файлу намеренно: у
   .cart__row--due та же специфичность, что у .cart__row--total, и при равной
   специфичности побеждает правило ниже — иначе «К оплате» осталась бы без верхней линии. */
.cart__row--dep{color:var(--color-secondary)}
.cart__row--due{border-top-color:var(--color-heading)}

/* Галочка «оплатить депозитом» — та же анатомия, что у .consent: flex-wrap обязателен,
   иначе подпись не переносится на свою строку, а лишь сжимает текст в узкую колонку.
   .opt («позвонить», «меньше упаковки» на оформлении) — ровно та же галочка с подписью,
   поэтому селекторы дописаны в эти группы, а не скопированы. */
.depbox,.opt{display:flex;flex-wrap:wrap;align-items:flex-start;gap:.6rem;
  font-size:.9rem;color:var(--color-text);line-height:1.5;margin:.35rem 0;
  min-height:var(--tap);padding-block:.35rem;cursor:pointer}
.depbox input[type=checkbox],.opt input[type=checkbox]{margin-block:.15rem}
.depbox__text,.opt__t{display:block;min-width:0;flex:1 1 12rem}
.depbox__text b,.opt__t b{display:block;font-weight:600;color:var(--color-heading)}
.depbox__note,.opt__x{display:block;margin-top:.2rem;font-size:.8rem;color:var(--muted);line-height:1.5}
/* На Тагл Пэй пусто — выбор недоступен, и это видно не только по disabled: курсор и
   приглушение объясняют, почему галочка не ставится. Нехватка на весь заказ галочку
   больше не гасит (остаток уходит картой) — подпись частичного покрытия печатаем
   обычным цветом текста, это не оговорка, а условие оплаты. */
.depbox.is-off{cursor:default}
.depbox.is-off .depbox__text b{color:var(--muted)}
.depbox.is-partial .depbox__note{color:var(--color-text)}

/* Пополнение депозита в кабинете. Поле суммы — обычная пилюля (padding из общего правила
   полей тут уместен, это не узкий степпер), но во всю ширину карточки число выглядит как
   адрес: ограничиваем меру. */
.dep{display:flex;flex-direction:column;gap:.25rem;margin:1.25rem 0 0}
.dep .field{margin:.6rem 0}
.dep__amt input{max-width:12rem}
.dep__mail input{max-width:22rem}
.dep .btn{align-self:flex-start;margin-top:.35rem}
.dep__note{margin-top:.6rem}

/* История операций. Раньше печатались только дата и сумма — после первого пополнения
   человек видел два безымянных «+500 ₽» и не мог отличить бонус от возврата. */
.acct__txn{display:flex;flex-direction:column;gap:.15rem;min-width:0}
.acct__txn-kind{font-weight:600;color:var(--color-heading);font-size:.88rem}
.acct__txn-meta{color:var(--muted);font-size:.78rem;line-height:1.45;overflow-wrap:anywhere}
/* flex:none обязателен: без него колонка суммы сжимается по остаточному принципу —
   замерено 35px, и «−15 505 ₽» переносилось на три строки, то есть показывало
   человеку другое число. tabular-nums выравнивает разряды в столбик. */
.acct__txn-amt{white-space:nowrap;flex:none;font-variant-numeric:tabular-nums;
  color:var(--color-secondary)}
.acct__txn-amt.is-minus{color:var(--color-heading)}

/* Оплата депозитом в итогах заказа. Строка настоящая, денежная — не приглушаем её
   вместе с остальным tfoot. Селектор через tfoot — (0,2,2): голый .otable__dep td (0,1,1)
   проигрывал .otable tfoot td (0,1,2), и строка оставалась приглушённой. */
.otable tfoot .otable__dep td{color:var(--color-text)}
/* Скидка по промокоду в итогах заказа — тоже настоящие деньги: tfoot приглушён, строка нет. */
.otable tfoot .otable__disc td{color:var(--color-text)}
/* «Вернулись на депозит …» — примечание под строкой списания, а не сумма: у него нет
   правой колонки, и оно намеренно тише. Ячейка одна и она ПЕРВАЯ, поэтому мобильное
   правило .otable td:nth-child(n+2){text-align:right} её не разворачивает. */
.otable__depback td{color:var(--muted);font-size:.8rem;line-height:1.5;
  padding-top:.15rem;border-bottom:0}

.formmsg{border-radius:10px;padding:.85rem 1.15rem;margin:0 0 1.5rem;font-size:.9rem;line-height:1.55}
.formmsg--ok{background:var(--color-secondary-soft);color:var(--color-secondary)}
.formmsg--err{background:var(--color-primary-soft);color:var(--color-primary-ink)}
.formmsg+.formmsg{margin-top:-.9rem}

/* toast: "added to cart · go to cart" */
/* Отступ снизу — через переменную: на телефоне тост обязан подняться над липкой
   панелью покупки, а .pdp .toast не сработает — тост лежит отдельным узлом в конце
   <body>, вне <main>. Класс вешает JS на корень (html.is-buybar). */
.toast{position:fixed;z-index:40;right:1.25rem;bottom:var(--toast-bottom,1.25rem);max-width:22rem;
  background:var(--color-heading);color:#fff;border-radius:14px;padding:.9rem 1.1rem;
  box-shadow:0 8px 32px rgba(0,0,0,.25);font-size:.9rem;line-height:1.4;
  display:flex;gap:.75rem;align-items:center;
  opacity:0;transform:translateY(8px);pointer-events:none;
  transition:opacity .25s var(--ease),transform .25s var(--ease)}
.toast.is-on{opacity:1;transform:none;pointer-events:auto}
.toast a{color:#fff;text-decoration:underline;white-space:nowrap;font-weight:600}
.toast a:hover{color:#fff;opacity:.8}
.toast :focus-visible{outline-color:#fff}
/* Текст живёт в live-области и должен ужиматься, а не выдавливать кнопки. */
.toast__msg{flex:1 1 auto;min-width:0}
.toast__undo,.toast__x{background:none;border:0;color:#fff;cursor:pointer;font-weight:600}
.toast__undo{text-decoration:underline;white-space:nowrap}
.toast__x{font-size:1.2rem;line-height:1;padding:.25rem;min-width:var(--tap);min-height:var(--tap);
  display:grid;place-items:center}

/* ---------- полоса про cookie ----------
   Это ИНФОРМИРОВАНИЕ, а не гейт согласия. Сторонней аналитики, рекламы и пикселей на
   сайте нет, а все куки (tc_lang, tc_shop, tc_uid, tc_preview, tc_shop_admin) строго
   необходимы: отключение ломает вход, корзину и выбор языка. Поэтому здесь ОДНА кнопка
   и нет кнопки «Отклонить» — она либо солгала бы (куки всё равно ставятся), либо
   сломала бы магазин; фальшивый выбор хуже его отсутствия. Отсюда и вид: узкая полоса
   без затемнения, без модальности и без перехвата фокуса.
   Фон тёмный (--color-heading), как шапка сайта: полоса читается как элемент
   интерфейса, а не как кусок контента, и не спорит с тёплой бумагой страницы.
   position:fixed, а не sticky: фиксированная полоса не участвует в потоке и не даёт
   ни пикселя CLS. Разметку печатает сервер и только при отсутствии куки tc_ck,
   поэтому мигания «показали — убрали» нет вовсе.
   z-index 45 — строго между тостом (40) и шторкой (50); «к содержимому» (60) выше всех. */
.ckbar{position:fixed;left:0;right:0;bottom:0;z-index:45;
  background:var(--color-heading);color:#fff}
.ckbar__in{max-width:var(--wrap);margin-inline:auto;padding-inline:var(--pad);
  display:flex;align-items:center;gap:1rem;min-height:var(--ck-h);
  padding-block:.75rem;padding-bottom:calc(.75rem + env(safe-area-inset-bottom))}
.ckbar__t{flex:1 1 auto;margin:0;font-size:.85rem;line-height:1.45}
.ckbar__t a{color:#fff;text-decoration:underline;
  text-decoration-color:rgba(255,255,255,.45);text-underline-offset:3px}
.ckbar__t a:hover{color:#fff;text-decoration-color:#fff}
/* Светлая пилюля: чёрная .btn на чёрной полосе не читается (тот же случай, что
   .btn--light на фото героя). Специфичность равна .btn (0,1,0), поэтому блок обязан
   стоять ПОСЛЕ секции кнопок — выигрывает порядком. */
.ckbar__ok{flex:none;background:#fff;border-color:#fff;color:var(--color-heading)}
.ckbar__ok:hover{background:var(--color-gray);border-color:var(--color-gray);
  color:var(--color-heading)}
/* Подложка тёмная — чернильное кольцо фокуса на ней не видно (приём из шапки). */
.ckbar :focus-visible{outline-color:#fff}

/* Полоса fixed и перекрывает низ страницы. Класс has-ck ставит СЕРВЕР ровно тогда,
   когда полоса напечатана, поэтому подъём соседей работает и без JS.
   padding у body — чтобы полоса не закрывала последнюю строку подвала. */
html.has-ck body{padding-bottom:var(--ck-h)}
html.has-ck{--toast-bottom:calc(var(--ck-h) + 1rem + env(safe-area-inset-bottom))}

/* На печати полосы нет — значит нет и резерва под неё, иначе внизу листа
   останется пустая полоса высотой в баннер. */
@media print{ .ckbar{display:none} html.has-ck body{padding-bottom:0} }
@media (forced-colors:active){
  .ckbar{background:Canvas;color:CanvasText;border-top:1px solid CanvasText}
  .ckbar__t a{color:LinkText}
  .ckbar__ok{background:ButtonFace;color:ButtonText;border-color:ButtonText}
}

/* ---------- responsive ---------- */
@media (min-width:48rem){
  .grid,.grid--3{grid-template-columns:repeat(3,1fr)}
  .starter__grid{grid-template-columns:repeat(3,1fr)}
  .hdr__nav{display:flex;gap:2.25rem;font-size:.82rem;text-transform:uppercase;letter-spacing:.1em;margin-left:1.5rem}
  .hdr__nav a{color:var(--header-ink)}
  .hdr__nav a:hover{color:var(--header-ink);opacity:.55}
  .hdr__burger{display:none}
  /* Колонка фото — 36vw в пределах 18–30rem, остальное — покупке. Было 1.05fr : 1fr, и на
     1280×860 кадр выходил 572×572 — почти весь экран; теперь 455 (замер), на широком — 480.
     Слот кадра объявлен в ProductPhoto::SIZES_ATTR — правится вместе с этим числом.
     Потолок «кадр не выше 70% высоты экрана» стоит НА ДОРОЖКЕ грида, а не на кадре: кадр —
     квадрат во всю ширину колонки, поэтому ограничить его ширину и значит ограничить высоту.
     Прежде max-width висел на .pdp__media, и на низком экране (1366×600) кадр 420px стоял
     в колонке 480px, прижатый влево: пустая полоса 60px перед колонкой покупки, а лента
     миниатюр — шире кадра над ней. Теперь колонка сама сужается до кадра, и зазор — один gap. */
  .pdp__grid{grid-template-columns:min(clamp(18rem,36vw,30rem), calc(var(--sv) * .7)) minmax(0,1fr)}
  /* Кадр — квадрат во всю колонку. Потолок по высоте — на дорожке выше, а не max-height здесь:
     при max-height коробка стала бы шире своей высоты, и cover на низком ноутбуке резал бы
     пачку. Телефонный max-height здесь снимается. */
  .pdp__media{max-height:none}
  .cards2{grid-template-columns:1fr 1fr}
  /* auto-fit keeps the footer honest whether it has three columns or four */
  .foot__in{grid-template-columns:repeat(auto-fit,minmax(11rem,1fr))}
  /* Сводка фиксированной шириной 21rem на 768px оставляла списку 313px, из них телу
     строки 104px — в строке теперь не только имя, но и чипы веса, селект помола и
     степпер. Ширина «Итога» сжимается на планшете и растёт до 22rem на десктопе (там в
     нём теперь ещё вкладки способа получения); minmax(0,1fr) не даёт колонке списка
     распираться содержимым. */
  .cart__main{grid-template-columns:minmax(0,1fr) clamp(17rem,28vw,22rem)}
  /* align-self обязателен: в grid элемент по умолчанию растянут на всю строку, и липнуть
     ему нечем. Сама липкость — ниже, только на экранах достаточной высоты. */
  .cart__side{align-self:start}
  .checkout{grid-template-columns:minmax(0,1fr) clamp(18rem,32vw,24rem);column-gap:clamp(2rem,4vw,4rem)}
  /* Сводка стоит в DOM ПЕРЕД формой (порядок табуляции), визуальный порядок
     возвращаем сеткой, а не order — иначе он разойдётся с порядком чтения. Сводка —
     грид-элемент строки 1 с align-self:start: липкость работает только так (§4l). */
  .checkout__form{grid-column:1;grid-row:1}
  .checkout__summary{grid-column:2;grid-row:1;align-self:start}
  .auth{grid-template-columns:1fr 1fr}
}
/* Итог и сводка не должны уезжать за экран при длинном списке — но липнут ТОЛЬКО там, где
   помещаются под шапкой. Липкая колонка выше вьюпорта не видна снизу, пока не кончится
   грид-строка: на 1366×768 (вьюпорт ~650px) сводка с Тагл Пэй (~552px) и «Итог» корзины с
   частичным Тагл Пэй (~610px) не влезают, и кнопка уезжала бы под край. На низком экране
   колонка просто прокручивается со страницей. */
@media (min-width:48rem) and (min-height:46rem){
  .cart__side,.checkout__summary{position:sticky;top:calc(var(--hdr-h) + 1rem)}
  /* Галерея товара едет рядом с колонкой покупки, пока та длиннее кадра. Тот же гейт по
     высоте: кадр с лентой миниатюр (~560px) под шапкой помещается только тут. align-self
     обязателен по той же причине, что у сводки корзины (§4l). */
  .pdp__gallery{position:sticky;top:calc(var(--hdr-h) + 1rem);align-self:start}
}
@media (min-width:64rem){
  .grid--3{grid-template-columns:repeat(3,1fr)}
  /* Кабинет расходится на колонки только здесь. На 768px рельс получал бы 338px, а
     таблице заказов оставалось столько же — одна колонка в 704px читается лучше. */
  .acct{grid-template-columns:minmax(0,1fr) clamp(18rem,26vw,22rem)}
  /* Рельса нет вовсе — депозит есть далеко не у каждого, а профиль и пароль уехали
     на /account/settings/. Без этого правила справа вставала бы дыра в 288–352px,
     а таблица заказов на столько же сжималась.
     Правило обязано лежать ЗДЕСЬ, а не рядом с .acct выше по файлу: специфичность
     у обоих (0,1,0), и при равной специфичности побеждает то, что ниже, — на этом
     в слайдере уже осталась видимой кнопка паузы с классом .vh.
     Модификатор из PHP, а не .acct:has(.acct__side): :has() отваливается в старых
     браузерах МОЛЧА и вернул бы ровно ту же дыру. */
  .acct--solo{grid-template-columns:minmax(0,1fr)}
  /* Описание и характеристики рядом: проза ≈41rem (мера чтения .prose), таблица — остаток.
     Доли, а не minmax(0,42rem) + 16rem: на 1024 фиксированный пол таблицы отнимал у прозы
     место и ломал капс-подписи на две строки. 1180 → 655 + 452, 1024 → 531 + 368. */
  .pdp__more--two{grid-template-columns:minmax(0,1.45fr) minmax(0,1fr);column-gap:clamp(3rem,6vw,6rem);
    align-items:start}
}

/* ---------- mobile (one block for the whole site) ---------- */
@media (max-width:47.999rem){
  /* iOS Safari zooms into fields smaller than 16px and never zooms back out */
  .sel,input[type=search],input[type=text],input[type=email],input[type=password],
  input[type=tel],input[type=number],input[type=url],textarea,.search__form input{font-size:16px}

  /* 44px touch targets. The header bar is already tight at 375px (the brand name must
     read in full), so its icons grow an invisible hit area instead of real width. */
  .ic::after,.hdr__burger::after{content:"";position:absolute;top:50%;left:50%;
    transform:translate(-50%,-50%);width:calc(100% + .4rem);height:var(--tap)}
  .fchip,.wchip{padding:.6rem 1rem;font-size:.85rem}
  .shopnav a{min-height:var(--tap);align-items:center}
  /* Тач-цель добираем ТОЛЬКО по вертикали: по горизонтали в шапке дефицит,
     и min-width обрезал надпись «Toggle Coffee» до «Toggle …» на 375px. */
  .ic{min-height:var(--tap)}
  .hdr__burger{min-height:var(--tap);align-content:center}
  .fchip,.wchip{min-height:var(--tap);display:inline-flex;align-items:center}
  .note{min-height:var(--tap)}
  .wchips--lg .wchip{padding:.6rem 1.1rem}
  .btn--sm{height:var(--tap);padding-inline:1.35rem}
  .pager a{min-width:var(--tap);height:var(--tap)}
  .cl__rm{min-height:var(--tap)}
  /* .search__close/.search__clear переехали в @media (pointer:coarse) в конце файла:
     44px им нужны и на iPad в портрете (ровно 768px — мимо этого порога). */
  .drawer__close{width:var(--tap);height:var(--tap);display:grid;place-items:center}
  /* Миниатюра стала ссылкой — тач-цель обязана вырасти и у неё, а не только у button. */
  .pdp__thumbs button,.pdp__thumbs .pdp__thumb{width:var(--tap);height:var(--tap)}
  /* WARNING: never touch the .qty input padding here — the digit disappears. */
  .cl__ctl .qty button,.cl__ctl .qty input{height:var(--tap);width:2.5rem}

  /* the smallest type gets a notch more air */
  .meter__l,.ac__k,.story__k,.article__k,
  .cfacts dt,.ccard__k,.creq__h{font-size:.75rem;letter-spacing:.1em}
  /* Контакты: пилюли шапки во всю ширину — палец не промахивается. Водяной знак
     самолёта на телефоне гасим: на 375px он ложится прямо под адрес. */
  .chead__act .btn{flex:1 1 100%}
  .ac__x{font-size:.9rem}
  .checkout__pay,.order__pay,.foot__bar-in{font-size:.8rem}

  .pc__row{flex-wrap:wrap}
  .pc__row .btn--sm{width:100%}
  /* каталог на телефоне: вкладки одной строкой с вылетом на поля, панель фасетов
     столбиком, все тач-цели 44px */
  .catnav{margin-inline:calc(-1*var(--pad));padding-inline:var(--pad);gap:1.25rem}
  .catnav__i{font-size:.875rem}
  /* панель фасетов должна раскрываться во ВСЮ ширину: во флекс-строке details.facets получал
     бы только левую колонку (~234px рядом с кнопкой сортировки). Поэтому .shopbar — блок,
     сортировка стоит абсолютом справа сверху, min-height держит место под неё, когда
     .facets не печатается (accessories). .sortdd__menu (absolute от .sortdd) работает как был. */
  .shopbar{position:relative;display:block;min-height:calc(var(--tap) + .7rem)}
  .sortdd{position:absolute;right:0;top:.35rem}
  .facets__btn{display:flex;width:max-content}
  .facets__btn,.sortdd__btn{min-height:var(--tap)}
  .facets__panel{flex-direction:column;align-items:stretch;gap:.85rem;padding:.25rem 0 1rem}
  /* gap .75rem = 12px: ровно столько снизу занимает тач-зона «?» (inset:-12px), до чипов она не достаёт */
  .facets__grp{flex-direction:column;align-items:stretch;gap:.75rem;min-height:0}
  /* контейнер поповера — строка подписи, а не вся группа (в колонке группа высокая, и поповер
     открывался бы под чипами, накрывая следующую группу); .facets__l растянут на ширину группы
     (align-items:stretch), поэтому left:0;right:0 дают всю ширину панели */
  .facets__l{flex:none;position:relative}
  /* width:auto обязателен: с базовым width:max-content right игнорируется и поповер уезжал за экран */
  .hint p{left:0;right:0;width:auto;max-width:none}
  .hint>summary::after{inset:-12px}
  .sortdd__menu a{min-height:var(--tap)}
  /* (0,2,0) обязана быть ЗДЕСЬ: десктопное .pc .wchip иначе перебьёт мобильное .wchip */
  .pc .wchip{min-height:var(--tap);padding:.6rem .8rem;font-size:.8125rem}
  .pc .btn--sm{height:var(--tap)}
  .abar{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;margin-inline:calc(-1*var(--pad));
    padding-inline:var(--pad);padding-block:3px}
  .abar>*{flex:none}
  /* кольцо фокуса внутрь пилюли: снаружи его режет overflow скроллера */
  .abar .fchip:focus-visible,.abar__reset:focus-visible{outline-offset:-3px}
  .abar__reset,.searchbar .searchbar__clear,.pc__wh{min-height:var(--tap)}
  /* Кикер в колонке 167px: при .72rem/.14em «ЭСПРЕССО · НАТУРАЛЬНЫЙ» ≈ 190px и ломался на
     две строки. Капс чуть мельче и плотнее — помещается в одну; подпись фасетов остаётся .72rem. */
  .facets__l{font-size:.72rem}
  .pc__k{font-size:.625rem;letter-spacing:.1em}
  /* Цена и пилюля наличия на телефоне — ВСЕГДА двумя строками. Иначе строка складывалась
     по содержимому: без старой цены пилюля стояла справа, а тап по варианту со скидкой
     добавлял зачёркнутую цену, пилюля не влезала в 335px и падала вниз — .pdp__buyhead
     вырастал на ~50px, и ряд чипов веса уезжал из-под пальца сразу после нажатия (замер
     на 375×812). Высота шапки покупки теперь одна при любой цене. */
  .pdp__pricebox{flex:1 0 100%}
  /* buy button stays under the thumb at any scroll depth */
  .pdp__buy{position:sticky;bottom:0;z-index:3;background:var(--bg);
    padding-block:.75rem;padding-bottom:calc(.75rem + env(safe-area-inset-bottom));
    box-shadow:0 -1px 0 var(--hair)}
  /* Сводка и так идёт первой в DOM — order больше не нужен. На телефоне она — свёрнутая
     полоса «Ваш заказ · сумма» на тёплой бумаге: рамочная карточка с полями 1.75rem над
     формой съедала бы первый экран. Поля переезжают во внутренний .osum__in, у полосы
     summary — свои. */
  .checkout__summary{margin-bottom:.5rem;padding:0;background:var(--color-gray)}
  .osum__in{padding:0 1.1rem 1.1rem}
  /* Итог и «К оформлению» — под большим пальцем, а не в конце длинной прокрутки.
     Приём тот же, что у .pdp__buy выше. */
  /* .cart — грид, а у грид-элемента контейнинг-блок это его собственная грид-область:
     она равна самому элементу, диапазона для липкости нет, и position:sticky не работал
     вовсе. У флекс-элемента контейнинг-блок — весь контейнер, поэтому на телефоне,
     где колонка всё равно одна, переводим .cart во флекс. Ровно поэтому .pdp__buy
     липнет исправно: его родитель — обычный блок. */
  .cart{display:flex;flex-direction:column}
  .cartbar{display:flex;align-items:center;gap:.9rem;position:sticky;bottom:0;z-index:3;
    background:var(--bg);padding-block:.75rem;
    padding-bottom:calc(.75rem + env(safe-area-inset-bottom));
    box-shadow:0 -1px 0 var(--hair)}
  .cartbar .btn{margin-left:auto;flex:1 1 auto;max-width:60%}
  /* Потолок выдачи считаем от ВИЗУАЛЬНОГО вьюпорта — единственного, что знает про
     экранную клавиатуру. На 375×667 с клавиатурой 300px доступно ~103px (две строки,
     обе видны), а прежние min(50vh,22rem) объявляли 333px, из которых была видна
     меньше трети. --vvh и --sbot пишет store.js; значения по умолчанию подобраны так,
     что формула верна и без JS. */
  .search__results{max-height:calc(var(--vvh,var(--sv)) - var(--sbot,9.4rem) - .5rem);min-height:0}
  /* Выдача поиска дотягивается до низа визуального вьюпорта, а полоса про cookie
     лежит поверх неё — последняя строка результатов оказывалась бы под плашкой.
     Резерв вычитается ровно тем же токеном, что поднимает липкие панели.
     Пол в 6rem обязателен: в ландшафте с открытой клавиатурой разность уходит
     в минус, а отрицательная max-height клампится в НОЛЬ — выдача исчезла бы
     целиком. Две строки под плашкой лучше, чем пустота. */
  html.has-ck .search__results{
    max-height:max(6rem,calc(var(--vvh,var(--sv)) - var(--sbot,9.4rem) - var(--ck-h) - .5rem))}
  .sr__row{min-height:var(--tap);align-items:center}
  .sr__chips .chip{min-height:var(--tap)}
  .sr__m{font-size:.75rem;letter-spacing:.1em}
  /* Ссылка, «Вернуть» и крестик неразрывны и на 375px съедали текст до колонки в 83px
     (тост вырастал до 130px в пять строк). Сообщение забирает первую строку целиком. */
  .toast{left:1rem;right:1rem;--toast-bottom:calc(1rem + env(safe-area-inset-bottom));
    max-width:none;flex-wrap:wrap;row-gap:.35rem}
  .toast__msg{flex-basis:100%}
  .toast__x{margin-left:auto}
  /* Липкая .pdp__buy лежит на z-index:3 и на 4 секунды съедала нажатия по кнопке
     покупки — тост встаёт над ней. На десктопе панель не липкая, правило не нужно. */
  html.is-buybar .toast{--toast-bottom:calc(5.5rem + env(safe-area-inset-bottom))}

  /* Полоса про cookie лежит поверх липких панелей: без подъёма покупатель на
     телефоне физически не дотянулся бы до «К оформлению» и «В корзину».
     У sticky нижний офсет работает так же, как у fixed: панель остановится
     ВЫШЕ полосы. Специфичность (0,2,1) перебивает исходные .cartbar/.pdp__buy. */
  html.has-ck .cartbar,
  html.has-ck .pdp__buy{bottom:var(--ck-h)}
  /* Тост: на телефоне --toast-bottom задан НА САМОМ .toast, и значение с html
     туда не дотянется — переменную приходится переопределять тоже на элементе.
     Комбинированный селектор (0,3,1) сильнее и .toast, и html.is-buybar .toast,
     поэтому порядок объявления роли не играет: на карточке товара с полосой
     тост встаёт над кнопкой покупки, а кнопка — над полосой. */
  html.has-ck .toast{--toast-bottom:calc(var(--ck-h) + 1rem + env(safe-area-inset-bottom))}
  html.has-ck.is-buybar .toast{--toast-bottom:calc(var(--ck-h) + 5.5rem + env(safe-area-inset-bottom))}
}
@media (max-width:36rem){
  .hero__cta .btn{flex:1 1 100%}
  .pageend .btn{flex:1 1 100%}
  /* Тот же приём, что у кнопок выше: в одну строку текст и пилюля здесь уже не
     помещаются, кнопка уезжает на свою строку во всю ширину. */
  .ckbar__in{flex-wrap:wrap}
  .ckbar__ok{width:100%}
  .cl{grid-template-columns:56px 1fr;gap:.9rem;align-items:start}
  .cl__media{width:56px;height:56px}
  /* Цена уходит под текст и прижата влево — значит и подпись «890 ₽ × 3» внутри неё
     равняется по левому краю, иначе колонка выглядит случайно отцентрованной. */
  .cl__price{grid-column:2;justify-self:start;margin-top:.5rem;align-items:flex-start}
  .cl__ctl{flex-wrap:wrap;gap:.75rem}
  .otable{font-size:.82rem}
  .otable th,.otable td{padding:.6rem .3rem}
  .otable td:nth-child(n+2){white-space:nowrap;text-align:right}
  /* Ниже этой ширины таблица истории шире экрана и .tablewrap прокручивается. Форма
     отмены обязана остаться в первом экране целиком, поэтому получает свою меру:
     без неё замер на 375px давал кнопку на x = 242…389, то есть за правым краем. */
  .canform--row{max-width:20rem}
  .article__nav{flex-direction:column;gap:.9rem}
  .article__next{margin-left:0;text-align:left}
  .article__nav a{display:block;padding-block:.5rem}
}
@media (max-width:22rem){
  .grid,.grid--3{grid-template-columns:1fr}
  .grid{gap:2rem}
  /* Отвоёвывает ~15px: на 320px полю остаётся ~210px, и укороченного плейсхолдера
     «Сорт, страна, статья» (~166px) хватает — полный текст ушёл в aria-describedby. */
  .search__form{gap:.5rem;padding-inline:1rem}
  .search__field{padding-inline:.8rem}
}

/* Ландшафт телефона и 400% зум.
   812×375: шапка 72.8 + форма + клавиатура ~200px не оставляют под выдачу НИЧЕГО
   (посчитано: −19px; на 568×320 −64px). Гейт тот же, что у слайдера, и БЕЗ orientation —
   иначе портретный зум 400% (320×200) в него не попадёт. Прячем верхний ярус шапки
   только пока идёт поиск: возвращается ~76px, то есть две строки вместо нуля. */
@media (max-height:30rem) and (min-aspect-ratio:5/8){
  html.is-searching .hdr__in{display:none}
  .search__form{padding-block:.35rem}
  .sr__row{padding-block:.3rem;min-height:0;grid-template-columns:36px minmax(0,1fr) auto}
  .sr__media{width:36px;height:36px}
  .sr__media--wide{height:24px}
  .sr__all{grid-template-columns:minmax(0,1fr);padding-left:calc(36px + .9rem)}
  .sr__t{-webkit-line-clamp:1}
}

/* ---------- motion ---------- */
@media (prefers-reduced-motion:no-preference){
  .drawer__panel{animation:slidein .22s var(--ease)}
  @keyframes slidein{from{transform:translateX(-100%)}}
}
@media (prefers-reduced-motion:reduce){
  *{transition:none!important;animation:none!important;scroll-behavior:auto!important}
  .pc__top:hover .pc__media>*,.ac:hover .ac__media img,.story:hover img{transform:none}
  /* Слайдер: автопрокрутки нет (её выключает store.js), значит останавливать нечего —
     кнопка паузы убирается. Смена слайда мгновенная: visibility не должна ждать
     несуществующего фейда, иначе уходящий слайд задержится на экране. */
  .hs__play{display:none}
  .hs__slide,.hs__slide.is-on{transition:none}
}

/* ---------- touch ----------
   Порог мобильного блока (47.999rem) отсекает iPad в портрете и сенсорный ноутбук:
   там «−»/«+» остаются 30×34px, а «Убрать» — около 22px. Гейт по вводу, а не по ширине.
   Специфичность равна исходным правилам корзины — выигрывает порядком, поэтому блок
   обязан стоять В КОНЦЕ файла. */
/* До 64rem подпись «Сортировка:» скрыта — на планшете полная кнопка (≈235px) отнимала
   у фасетов место, и «Страна» падала на вторую строку. Значение + шеврон остаются. */
@media (max-width:63.999rem){
  .sortdd__l{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
}
/* На десктопе панель фасетов раскрыта всегда, и активные чипы уже видны в ней с «×» —
   в полосе .abar остаётся только «Сбросить всё», иначе те же чипы печатались дважды. */
@media (min-width:48rem){
  .abar .fchip{display:none}
  /* Кроме ноты: в панели фасетов её нет, и без этого чипа фильтр на десктопе был бы
     невидим и неснимаем. (0,3,0) перебивает строку выше. */
  .abar .fchip.fchip--note{display:inline-flex}
  .abar__reset{margin-left:0}
}
@media (pointer:coarse){
  .cl__rm{min-height:var(--tap)}
  /* Каталог: iPad в портрете — ровно 768px и в мобильный порог не попадает, а чипы там
     31–35px. Гейт по вводу, а не по ширине; блок в конце файла — порядок важен. */
  .fchip,.pc .wchip,.sortdd__menu a,.note{min-height:var(--tap)}
  .pc .btn--sm{height:var(--tap)}
  .hint>summary::after{inset:-12px}
  /* Пилюля .qty задана высотой 3.25rem: без auto внутри неё останутся мёртвые 9px
     сверху и снизу, и нажатие мимо кнопки не сработает. */
  .cl__ctl .qty{height:auto}
  /* WARNING: не трогать padding у .qty input — цифра исчезнет. */
  .cl__ctl .qty button,.cl__ctl .qty input{height:var(--tap);width:2.5rem}
  /* Поиск: гейт по вводу, а не по ширине. iPad в портрете — ровно 768px и в мобильный
     порог не попадает, а крестик там раньше был примерно 11×22px. У «Закрыть» это
     min-width, а не width: внутри слово, и жёсткая коробка 44px его бы выдавила. */
  .search__clear{width:var(--tap);height:var(--tap);display:grid;place-items:center}
  .search__close{min-width:var(--tap);min-height:var(--tap);display:grid;place-items:center}
}

/* ---------- история заказов в кабинете на телефоне ----------
   Ниже 48rem таблица из четырёх колонок шире экрана, и .tablewrap прокручивался бы вбок.
   Перестраиваем ТУ ЖЕ разметку в карточки-строки: thead прячем, ячейки печатают свою
   подпись из data-label. Второго DOM для телефона не заводим — он разошёлся бы с первым
   в первую же правку.
   Блок стоит В КОНЦЕ ФАЙЛА намеренно. Выше по файлу, в @media (max-width:36rem), лежит
   .otable td:nth-child(n+2){white-space:nowrap;text-align:right} — это (0,2,1), и правило
   .otable--acct td (0,1,1) его не перебило бы. Селектор с [data-label] даёт ровно ту же
   специфичность (0,2,1), поэтому решает ПОРЯДОК: наше правило обязано стоять ниже. */
@media (max-width:47.999rem){
  .otable--acct,.otable--acct tbody,.otable--acct tr,.otable--acct td{display:block;width:auto}
  .otable--acct thead{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;
    clip-path:inset(50%)}
  .otable--acct tbody tr{border-bottom:1px solid var(--hair);padding:.85rem 0}
  /* Подпись ячейки — АБСОЛЮТОМ, а не колонкой грида. Замерено на 375px: у грида
     ::before забирает первую колонку, и КАЖДЫЙ следующий ребёнок ячейки становится
     отдельным элементом сетки — превью состава заказа уезжало во вторую строку первой
     колонки и получало 88px ширины вместо 235, ломаясь после каждого слова.
     С абсолютной подписью ячейка остаётся обычным блоком, и содержимое течёт как в
     таблице: ссылка, за ней превью, всё на полной мере. */
  .otable--acct td[data-label]{position:relative;border-bottom:0;
    padding:.2rem 0 .2rem 5.75rem;text-align:left;white-space:normal}
  .otable--acct td[data-label]::before{content:attr(data-label);position:absolute;
    left:0;top:.2rem;width:5.5rem;color:var(--muted);
    font-size:.66rem;text-transform:uppercase;letter-spacing:.1em;line-height:1.9}
  /* Строка-«шапка» и строка отмены — одна запись для человека: линия только под парой. */
  .otable--acct tbody tr.otable__lead{border-bottom:0;padding-bottom:.2rem}
  .otable--acct tbody tr.otable__cancelrow{padding-top:.35rem}
  .otable--acct tr.otable__cancelrow td{padding:0}
  /* Карточки уже по ширине экрана — прокручивать нечего, и лишний скролл-контейнер
     только съедал бы тень фокуса у ссылок внутри. */
  .tablewrap:has(.otable--acct){overflow-x:visible}
}

/* ════════ ОПТ (30.09.2026): /price/, статус в кабинете, плашки опта в корзине и заказе ════════
   Весь оптовый CSS витрины — ОДНИМ блоком здесь, в конце файла, и это не небрежность:
   ниже по файлу стоят только правила, которые обязаны перебивать мобильные и сенсорные
   блоки выше при равной специфичности (.qty, .btn--sm, .sel--sm), — при равной
   специфичности решает порядок (ловушка §1 карты: .vh против .hs__play).
   Разметку рисуют две зоны: price.php / account.php / product.php / layout.php — эти
   правила; .wsnote / .invbox / третья .paycard — cart.php, checkout.php, order_status.php
   (классы — контракт §4 спецификации). Токены — общие: Mont, волосяные линии, пилюли,
   терракота дозированно. Секционные обёртки — только padding-block (шорткат обнулил бы
   padding-inline у .wrap). */

/* Бейдж «Опт» — статус покупателя (кабинет, шапка прайса). Тёмная пилюля: это не акция и
   не предупреждение, а принадлежность — как подпись, а не как скидка. */
.wschip{display:inline-flex;align-items:center;font-size:.66rem;font-weight:700;
  text-transform:uppercase;letter-spacing:.14em;line-height:1.5;padding:.2rem .7rem;
  border-radius:999em;background:var(--color-heading);color:#fff;white-space:nowrap}

/* Пилюля «Опт» в тёмной шапке — пятая ссылка .hdr__nav, только одобренному оптовику.
   Обводка, а не заливка: на чёрной шапке белая плита спорила бы с логотипом. Подсветка на
   наведение — инверсией, а не опацити .55 соседей: (0,3,0) перебивает .hdr__nav a:hover.
   Видна только от 64rem, хотя .hdr__nav открывается с 48rem, — это замер, а не вкус: на 768px
   шапка уже заполнена до пикселя (RU — 0px запаса, в EN «Toggle Coffee» и без пилюли сжато
   на 27px), а пилюле с отступом нужно 88px (RU) и 142px (EN, «WHOLESALE»). Втиснутая, она
   обрезала название до «To…» и переносила «О НАС» на две строки. Помещается с ~856px (RU)
   и ~937px (EN); 64rem — с запасом на крупный системный шрифт. Ниже — прайс в подвале,
   а статус и ссылка — в шапке кабинета. */
.hdr__nav .hdr__ws{display:none}
@media (min-width:64rem){
  .hdr__nav .hdr__ws{display:inline-flex;align-items:center;padding:.05rem .65rem;
    border:1px solid rgba(255,255,255,.55);border-radius:999em;font-size:.72rem;letter-spacing:.14em;
    line-height:1.6;white-space:nowrap}
  .hdr__nav .hdr__ws:hover{background:#fff;border-color:#fff;color:var(--header-bg);opacity:1}
}

/* ---- /price/: шапка, рассказ, шаги ---- */
.wstop{margin-bottom:clamp(1.5rem,3vw,2.25rem)}
.wstop h1{margin-bottom:.4em}
.wstop__who{display:flex;flex-wrap:wrap;align-items:center;gap:.4rem .7rem;margin:0 0 1rem}
.wstop__co{font-size:.85rem;font-weight:600;color:var(--color-heading);overflow-wrap:anywhere}
.wstop__lead{margin:0;max-width:60ch;font-size:1rem;line-height:1.6;color:var(--color-text)}
.wstop__facts{margin:.6rem 0 0;font-size:.85rem;color:var(--muted);line-height:1.5}
/* Рассказ из админки — мера чтения, а не вся --wrap: страница широкая ради таблицы и форм. */
.wstop__pitch{max-width:44rem}
/* Общие сообщения страницы («заявка отправлена») — той же меры, что карточка статуса и форма,
   а не полосой во всю --wrap над карточкой в 46rem. */
.wspage>.formmsg{max-width:46rem}

.wssteps{list-style:none;margin:0 0 clamp(2rem,4vw,3rem);padding:0;display:grid;gap:1rem;
  grid-template-columns:minmax(0,1fr);counter-reset:wsstep}
.wssteps li{counter-increment:wsstep;display:grid;grid-template-columns:auto minmax(0,1fr);
  column-gap:.9rem;row-gap:.15rem;align-items:baseline;align-content:start;
  padding-top:1rem;border-top:1px solid var(--hair)}
/* align-content:start — пункты ряда тянутся по самому высокому соседу, и без него лишняя
   высота делилась между строками: описание короткого шага съезжало вниз (замер). */
/* Номер шага — единственная терракота страницы: маленький, но ведёт взгляд по порядку. */
.wssteps li::before{content:counter(wsstep,decimal-leading-zero);grid-row:1;
  font-size:.75rem;font-weight:700;letter-spacing:.1em;color:var(--color-primary-ink)}
.wssteps b{font-size:.95rem;color:var(--color-heading);letter-spacing:var(--lsp-head)}
.wssteps span{grid-column:2;font-size:.85rem;line-height:1.55;color:var(--muted)}
@media (min-width:48rem){ .wssteps{grid-template-columns:repeat(3,minmax(0,1fr));gap:1.5rem} }

/* ---- /price/: карточка статуса заявки ----
   Анатомия .paybox со страницы заказа (цветная кромка слева + тёплая бумага): смысл состояния
   написан словами в пилюле и заголовке, цвет — только оттенок, поэтому карточка понятна и
   без различения цветов, и в forced-colors. */
.wsstate{border:1px solid var(--color-border);border-left:3px solid var(--color-border);
  border-radius:var(--rounded);background:var(--color-gray);padding:1.25rem 1.35rem;
  margin:0 0 2.25rem;max-width:46rem}
.wsstate--pending{border-left-color:var(--color-heading)}
.wsstate--rejected{border-left-color:var(--color-primary);background:var(--color-primary-soft)}
/* Пилюля «Отклонена» на розовой плашке: терракота даёт 4.2:1, мелкому тексту нужно 4.5 —
   затемнённый тон, как у .pcb--low. */
.wsstate--rejected .ostatus--new{color:var(--color-primary-ink);border-color:var(--color-primary-ink)}
.wsstate--revoked{border-left-color:var(--faint)}
.wsstate__top{display:flex;flex-wrap:wrap;align-items:center;gap:.4rem .75rem;margin:0 0 .75rem}
.wsstate__date{font-size:.8rem;color:var(--muted)}
.wsstate__t{font-size:1.2rem;margin:0 0 .4rem}
.wsstate__reason{margin:0 0 .5rem;font-size:.9rem;line-height:1.55;color:var(--color-primary-ink);
  overflow-wrap:anywhere}
.wsstate__x{margin:0;font-size:.9rem;line-height:1.6;color:var(--color-text);max-width:60ch}
.wsstate__rows{display:grid;grid-template-columns:minmax(0,1fr);gap:.2rem 1.25rem;
  margin:1.1rem 0 0;padding-top:1rem;border-top:1px solid var(--color-border)}
.wsstate__rows dt{font-size:.66rem;font-weight:600;text-transform:uppercase;letter-spacing:.12em;
  color:var(--muted);margin-top:.5rem}
.wsstate__rows dd{margin:0;font-size:.9rem;color:var(--color-heading);overflow-wrap:anywhere}
.wsstate__act{margin:1.25rem 0 0}
@media (min-width:36rem){
  /* Пара «подпись — значение» в строку: 11rem хватает на «Юридический адрес» капсом. */
  .wsstate__rows{grid-template-columns:11rem minmax(0,1fr)}
  .wsstate__rows dt{margin-top:0;line-height:2}
  .wsstate__rows dd{line-height:1.6;padding-block:.15rem}
}

/* ---- /price/: формы ----
   Поля, подписи, ошибки и согласие — общие (.field, .frow, .consent, .formmsg): формы опта
   обязаны выглядеть и вести себя как регистрация и оформление, а не как третий стиль. */
.wsjoin{display:grid;gap:2rem;grid-template-columns:minmax(0,1fr)}
/* «Уже есть аккаунт» — первым и на телефоне: человеку с аккаунтом незачем листать форму. */
.wsjoin__side{align-self:start;background:var(--color-gray);border-radius:var(--rounded);
  padding:1.35rem 1.4rem}
.wsjoin__side h2,.wsjoin__form h2,.wsapply h2{font-size:1.4rem;letter-spacing:var(--lsp-head);
  margin-bottom:.75rem}
.wsjoin__side p{margin:0 0 1.1rem;font-size:.9rem;line-height:1.6;color:var(--color-text)}
@media (min-width:48rem){
  .wsjoin{grid-template-columns:minmax(0,1fr) minmax(0,2fr);gap:3rem}
  .wsjoin__side h2{font-size:1.15rem}
}
.wsjoin__form,.wsapply{min-width:0}
.wsapply{max-width:46rem}
.wsapply__x{margin:0 0 1.25rem;font-size:.9rem;line-height:1.6;color:var(--muted);max-width:60ch}
.wsform{max-width:46rem}
.wsform .cform{margin-bottom:1rem}
.wsform>.btn{margin-top:.75rem}
/* Выпадающий список вида деятельности — та же пилюля, что поля рядом (.sel задаёт вид,
   ширина по колонке .frow). */
.wsform .sel{width:100%}

/* ---- /price/: таблица прайса ----
   Разметка — контракт со store.js (§2.6): [data-ws-lot] > [data-ws-pos] > .qty[data-ws-qty].
   Классы ниже — только вид. Три раскладки лота, по замеру:
   — телефон (<48rem): карточка — имя, позиции (подпись над ценой, степпер справа), помол
     и кнопка строкой внизу;
   — планшет (48–64rem): имя во всю ширину сверху, под ним позиции и колонка действия.
     Три колонки здесь не помещаются: на 768px подписи «Зерно · 200 г» доставалось 36px,
     и она ломалась на три строки;
   — десктоп (≥64rem): строка сетки из трёх колонок (лот · позиции · действие), позиции —
     своя сетка (фасовка · цена · количество), ряд подписей .wscols повторяет те же дорожки. */
.wscat{margin-top:clamp(1.75rem,3.5vw,2.75rem)}
.wscat__h{font-size:clamp(1.15rem,2.6vw,1.6rem);letter-spacing:-.03em;margin:0;
  padding-bottom:.6rem;border-bottom:1px solid var(--color-heading)}
.wscols{display:none}
.wslot{display:grid;grid-template-columns:minmax(0,1fr);gap:.9rem;padding-block:1.25rem;
  border-bottom:1px solid var(--hair)}
.wslot__name{font-size:1.05rem;margin:0;letter-spacing:var(--lsp-head)}
.wslot__name a{color:var(--color-heading)}
.wslot__name a:hover{text-decoration:underline;text-underline-offset:3px;color:var(--color-heading)}
.wslot__meta{margin:.25rem 0 0;font-size:.8rem;line-height:1.45;color:var(--muted)}
.wslot__pos{display:grid;gap:.65rem;min-width:0}
/* Телефон: слева подпись над ценой, справа степпер на обе строки. */
.wspos{display:grid;grid-template-columns:minmax(0,1fr) auto;
  grid-template-areas:"l q" "p q";column-gap:1rem;row-gap:.1rem;align-items:center}
.wspos__l{grid-area:l;font-size:.9rem;line-height:1.4;color:var(--color-heading)}
.wspos__s{display:block;font-size:.75rem;line-height:1.4;color:var(--color-primary-ink)}
.wspos__p{grid-area:p;font-variant-numeric:tabular-nums;white-space:nowrap;line-height:1.4}
.wspos__p b{font-size:1rem;font-weight:700;letter-spacing:-.01em;color:var(--color-heading)}
/* «розн.» — позиция без опта продаётся по рознице (§1): тихая метка рядом с ценой, чтобы
   розничную цену не приняли за оптовую. От 48rem — строкой ПОД ценой: колонка цен выровнена
   вправо, и метка в строке сдвигала бы число влево от соседних. */
.wspos__r{margin-left:.4rem;font-size:.66rem;font-weight:600;text-transform:uppercase;
  letter-spacing:.1em;color:var(--muted);cursor:help}
.wspos__q{grid-area:q;justify-self:end}
.wspos.is-out .wspos__l,.wspos.is-out .wspos__p b{color:var(--muted)}
.wspos.is-out .wspos__s{color:var(--muted)}
/* Степпер прайса компактнее пилюли страницы товара (3.25rem): в строке их по два-три.
   ВНИМАНИЕ: padding у .qty input НЕ ТРОГАТЬ — глобальный отступ съел бы цифру (§1). */
.wspos .qty{height:2.5rem}
.wspos .qty button{width:2.25rem}
.wspos .qty input{width:2.75rem}
.wspos .qty button:disabled,.wspos .qty input:disabled{opacity:.35;cursor:not-allowed}
.wspos .qty button:disabled:hover{background:none}
.wslot__act{display:flex;flex-wrap:wrap;align-items:flex-end;gap:.6rem .75rem}
.wslot__grind{display:flex;flex-direction:column;gap:.3rem;flex:1 1 10rem;min-width:0}
.wslot__gl{font-size:.66rem;font-weight:600;text-transform:uppercase;letter-spacing:.12em;color:var(--muted)}
.wslot__grind .sel--sm{width:100%;min-width:0}
.wslot__add{flex:1 1 8rem}
@media (min-width:48rem){
  .wslot{grid-template-columns:minmax(0,1fr) 11rem;grid-template-areas:"h h" "p a";
    column-gap:2rem;align-items:start}
  .wslot__head{grid-area:h}
  .wslot__pos{grid-area:p}
  .wslot__act{grid-area:a}
  .wscols__pos,.wspos{display:grid;grid-template-columns:minmax(0,1fr) 7rem 7.75rem;column-gap:1rem}
  .wspos{grid-template-areas:"l p q";align-items:center}
  .wspos__p{text-align:right}
  .wspos__r{display:block;margin:.1rem 0 0;font-size:.6rem;line-height:1.3}
  /* Кнопка и помол — столбиком в своей колонке: кнопка одна на лот, под всеми позициями. */
  .wslot__act{flex-direction:column;align-items:stretch;flex-wrap:nowrap}
  .wslot__grind,.wslot__add{flex:none}
  .wslot__add{width:100%}
}
@media (min-width:64rem){
  .wscols,.wslot{grid-template-columns:minmax(0,1fr) minmax(0,1.9fr) 11rem;column-gap:2rem}
  .wslot{grid-template-areas:"h p a"}
  .wscols{display:grid;padding-block:.75rem .5rem;border-bottom:1px solid var(--hair);
    font-size:.66rem;font-weight:600;text-transform:uppercase;letter-spacing:.12em;color:var(--muted)}
  .wscols__pos>span:nth-child(2){text-align:right}
  .wscols__pos>span:nth-child(3){text-align:center}
}

/* ---- /price/: липкая полоса корзины ----
   position:sticky у ПРЯМОГО ребёнка секции-блока .wspage: у элемента грида липкость не
   работает (контейнинг-блок — его собственная грид-область, §4l). Полоса едет у нижнего
   края, пока видна таблица, и встаёт на место в конце прайса — подвал не перекрывает.
   Полоса про cookie (fixed, --ck-h) лежит поверх — подъём тем же токеном, что у .cartbar. */
.wsbar{position:sticky;bottom:0;z-index:3;display:flex;align-items:center;gap:.75rem 1.25rem;
  margin-top:1.5rem;padding-block:.85rem;padding-bottom:calc(.85rem + env(safe-area-inset-bottom));
  background:var(--bg);box-shadow:0 -1px 0 var(--hair)}
html.has-ck .wsbar{bottom:var(--ck-h)}
.wsbar__t{margin:0;display:flex;flex-direction:column;min-width:0;line-height:1.3;
  font-variant-numeric:tabular-nums}
.wsbar__t [data-wsbar-n]{font-size:.8rem;color:var(--muted)}
.wsbar__t [data-wsbar-sum]{font-size:1.1rem;font-weight:700;letter-spacing:-.02em;color:var(--color-heading)}
.wsbar__cart{margin-left:auto;font-size:.85rem;font-weight:600;white-space:nowrap;
  text-decoration:underline;text-decoration-color:var(--color-border);text-underline-offset:4px}
.wsbar__cart:hover{color:var(--color-heading);text-decoration-color:var(--color-heading)}
.wsbar__go{flex:none}
/* Тост «Добавлено» встаёт над полосой, а не на неё: иначе 4 секунды он закрывал бы именно
   «Оформить заказ». :has() работает с первой отрисовки, до JS; для браузеров без :has()
   (Safari < 15.4, Firefox < 121) запасной путь — класс html.is-wsbar из store.js.
   На телефоне --toast-bottom задан на самом .toast (блок ≤47.999rem), поэтому
   переопределяем тоже на элементе: (0,3,1) и (0,4,1) сильнее тех правил. */
html:has(.wsbar:not([hidden])) .toast{--toast-bottom:calc(5.5rem + env(safe-area-inset-bottom))}
html.has-ck:has(.wsbar:not([hidden])) .toast{
  --toast-bottom:calc(var(--ck-h) + 5.5rem + env(safe-area-inset-bottom))}
html.is-wsbar .toast{--toast-bottom:calc(5.5rem + env(safe-area-inset-bottom))}
html.has-ck.is-wsbar .toast{--toast-bottom:calc(var(--ck-h) + 5.5rem + env(safe-area-inset-bottom))}
/* Фокус с клавиатуры не должен уходить ПОД полосу (WCAG 2.4.11): при Tab браузер докручивает
   поле ровно до нижнего края окна — туда, где лежит полоса. Резервируем её высоту снизу, как
   шапку сверху (scroll-padding-top у html выше); задаём только нижнее значение — верхнее живо. */
html:has(.wsbar:not([hidden])){scroll-padding-bottom:calc(5.5rem + env(safe-area-inset-bottom))}
html.has-ck:has(.wsbar:not([hidden])){scroll-padding-bottom:calc(var(--ck-h) + 5.5rem + env(safe-area-inset-bottom))}
html.is-wsbar{scroll-padding-bottom:calc(5.5rem + env(safe-area-inset-bottom))}
html.has-ck.is-wsbar{scroll-padding-bottom:calc(var(--ck-h) + 5.5rem + env(safe-area-inset-bottom))}

@media (max-width:47.999rem){
  /* Тач-цели 44px: степпер, кнопка лота и помол. Высоту пилюли .qty — auto, иначе мёртвые
     поля сверху и снизу (приём .cl__ctl). padding у input по-прежнему не трогаем. */
  .wspos .qty{height:auto}
  .wspos .qty button,.wspos .qty input{height:var(--tap);width:2.75rem}
  .wslot__add{height:var(--tap)}
  .wslot__grind .sel--sm{height:var(--tap)}
  .wsform>.btn{width:100%}
  .wsjoin__side .btn{width:100%}
  /* Полоса: «N позиций / сумма» столбиком слева, «Корзина» и «Оформить заказ» справа. */
  .wsbar{gap:.75rem}
  .wsbar__go{padding-inline:1.25rem}
  /* «Корзина» — тач-цель 44px, как у степпера; высоту полосы задаёт кнопка (48px). */
  .wsbar__cart{display:inline-flex;align-items:center;min-height:var(--tap)}
}
@media (max-width:22rem){
  /* 320px: ссылка «Корзина» уступает место кнопке — корзина есть и в шапке. */
  .wsbar__cart{display:none}
  .wsbar__go{margin-left:auto}
}
/* iPad в портрете — ровно 768px и в мобильный порог не попадает: гейт по вводу. */
@media (pointer:coarse){
  .wspos .qty{height:auto}
  .wspos .qty button,.wspos .qty input{height:var(--tap);width:2.75rem}
  .wslot__add{height:var(--tap)}
  .wslot__grind .sel--sm{height:var(--tap)}
  /* Без display: блок стоит ниже правила 22rem, которое прячет ссылку на 320px. min-height
     у флекс-элемента полосы работает и без inline-flex, строку по центру ставит line-height. */
  .wsbar__cart{min-height:var(--tap);line-height:var(--tap)}
}
@media (pointer:coarse) and (min-width:48rem){
  /* Степпер 44px шире колонки 7.75rem (2.75 × 3 + рамка) — колонке нужно 8.5rem. */
  .wscols__pos,.wspos{grid-template-columns:minmax(0,1fr) 7rem 8.5rem}
}

/* ---- страница товара: подпись опта и «только опт» ---- */
/* «Оптовая цена» под ценой — у одобренного оптовика. Тихий капс с точкой: подпись к цене,
   а не бейдж, на который хочется нажать. */
.pdp__ws{display:inline-flex;align-items:center;gap:.45rem;margin:.55rem 0 0;font-size:.68rem;
  font-weight:600;text-transform:uppercase;letter-spacing:.14em;color:var(--color-heading);line-height:1.4}
.pdp__ws::before{content:"";width:.4rem;height:.4rem;border-radius:50%;background:var(--color-primary);flex:none}
.pdp__wh{margin-top:.75rem}
.pdp__whnote{margin:0 0 .85rem;font-size:.9rem;line-height:1.6;color:var(--color-text)}

/* ---- кабинет: строка оптового статуса под именем ---- */
.acct__ws{flex-basis:100%;margin:.15rem 0 .35rem;display:flex;flex-wrap:wrap;align-items:center;
  gap:.35rem .6rem;font-size:.85rem;line-height:1.5;color:var(--color-text)}
.acct__wslink{font-weight:600;white-space:nowrap;text-decoration:underline;
  text-decoration-color:var(--color-border);text-underline-offset:4px}
.acct__wslink:hover{color:var(--color-heading);text-decoration-color:var(--color-heading)}
@media (max-width:47.999rem){ .acct__wslink{min-height:var(--tap);display:inline-flex;align-items:center} }

/* ---- корзина и оформление: плашка опта (разметка — cart.php / checkout.php) ----
   Сообщение, а не предупреждение: тёмная кромка слева, тёплая бумага — как ждущая оплаты
   плашка заказа. Промокод и Тагл Пэй у опта не действуют, и плашка говорит это заранее. */
.wsnote{margin:0 0 1.25rem;padding:.8rem 1rem .8rem 1.1rem;border:1px solid var(--hair);
  border-left:3px solid var(--color-heading);border-radius:10px;background:var(--color-gray);
  font-size:.85rem;line-height:1.55;color:var(--color-text);overflow-wrap:anywhere}
.wsnote b,.wsnote strong{color:var(--color-heading)}

/* ---- страница заказа: блок счёта (разметка — order_status.php) ----
   Родня .paybox: кромка слева, карточка на тёплой бумаге. Реквизиты — парами «подпись —
   значение»; длинные номера счёта и название компании переносятся где угодно, а не
   распирают колонку. */
.invbox{border:1px solid var(--color-border);border-left:3px solid var(--color-heading);
  border-radius:var(--rounded);background:var(--color-gray);padding:1.15rem 1.25rem;
  margin:0 0 1.75rem;display:flex;flex-direction:column;gap:.75rem}
.invbox__head{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:.35rem 1rem}
.invbox__head>*{margin:0}
.invbox__head h2,.invbox__head h3{font-size:1rem;font-weight:700;letter-spacing:var(--lsp-head)}
/* Кроме .paybox__note: подпись плашки остаётся приглушённой, как у прочих .paybox. */
.invbox p:not(.paybox__note){margin:0;font-size:.85rem;line-height:1.6;color:var(--color-text)}
.invbox__rows{display:grid;grid-template-columns:minmax(0,1fr);gap:.15rem 1.25rem;margin:0}
.invbox__rows dt{font-size:.66rem;font-weight:600;text-transform:uppercase;letter-spacing:.12em;
  color:var(--muted);margin-top:.45rem}
.invbox__rows dd{margin:0;font-size:.9rem;color:var(--color-heading);overflow-wrap:anywhere;
  font-variant-numeric:tabular-nums}
.invbox .btn{max-width:22rem}
.invbox form{margin:0}
/* Оплачен / аннулирован — если разметка ставит модификатор; без него карточка нейтральна. */
.invbox--paid{border-left-color:var(--color-secondary);background:var(--color-secondary-soft)}
.invbox--canceled{border-left-color:var(--color-border)}
@media (min-width:36rem){
  .invbox__rows{grid-template-columns:10rem minmax(0,1fr)}
  .invbox__rows dt{margin-top:0;line-height:2}
  .invbox__rows dd{line-height:1.6;padding-block:.15rem}
}

@media (forced-colors:active){
  /* Фоны гасятся — карточкам и плашкам нужна рамка, пилюле «Опт» — системная пара. */
  .wschip{forced-color-adjust:none;background:CanvasText;color:Canvas}
  .wsstate,.wsnote,.invbox,.wsjoin__side{border:1px solid CanvasText}
  .pdp__ws::before{forced-color-adjust:none;background:CanvasText}
}
@media print{
  /* Печатают прайс ради цен: полоса корзины и кнопки лотов на бумаге не нужны. */
  .wsbar,.wslot__act,.wspos__q{display:none!important}
}

/* Antibot: honeypot-поле регистрации. Не display:none и не visibility:hidden — такие
   поля боты пропускают; уносим за экран, человек его не видит и не нажмёт. */
.hp{position:absolute;left:-10000px;top:auto;width:1px;height:1px;overflow:hidden}

/* ─── «Добавить на главный экран» (§4ab) ──────────────────────────────────────
   Плашка над подвалом. Приходит из PHP скрытой: показывает её только a2hs.js и
   только на телефоне, где это выполнимо (iPhone/Android, не встроенный вебвью).
   Панель с шагами раскрывается на месте — модалок на сайте нет, и заводить их
   ради подсказки незачем. */
.a2hs{margin-top:3rem}
.a2hs__bar{
  display:flex;align-items:center;gap:.875rem;width:100%;
  padding:.9rem 1.1rem;font:inherit;text-align:left;cursor:pointer;
  background:var(--color-gray);border:1px solid var(--color-border);border-radius:.75rem;
  color:var(--color-heading);
}
.a2hs__bar:hover{background:var(--color-gray-2);border-color:var(--color-heading)}
.a2hs__ico{
  flex:0 0 auto;width:2.25rem;height:2.25rem;display:flex;align-items:center;justify-content:center;
  background:var(--color-white);border:1px solid var(--color-border);border-radius:50%;
}
.a2hs__ico svg{width:1.125rem;height:1.125rem;display:block}
.a2hs__txt{display:flex;flex-direction:column;gap:.15rem;min-width:0}
.a2hs__txt b{font-size:.95rem;font-weight:700}
.a2hs__txt small{color:var(--color-text);font-size:.8rem;line-height:1.35}
.a2hs__go{margin-left:auto;flex:0 0 auto;font-size:1.25rem;line-height:1;color:var(--color-text)}
.a2hs__bar[aria-expanded="true"] .a2hs__go{transform:rotate(45deg)}
.a2hs__panel{
  margin-top:.5rem;padding:1rem 1.1rem 1.25rem;
  background:var(--color-white);border:1px solid var(--color-border);border-radius:.75rem;
}
.a2hs-intro{margin:0 0 .5rem;color:var(--color-text);font-size:.875rem;line-height:1.5}
.a2hs-steps{list-style:none;margin:0;padding:0;counter-reset:a2hs}
.a2hs-steps li{
  display:flex;align-items:flex-start;gap:.75rem;
  padding:.75rem 0;border-top:1px solid var(--hair);font-size:.9rem;line-height:1.45;
}
.a2hs-steps li::before{
  counter-increment:a2hs;content:counter(a2hs);
  flex:0 0 auto;width:1.5rem;height:1.5rem;margin-top:.05rem;
  display:flex;align-items:center;justify-content:center;border-radius:50%;
  background:var(--color-gray);color:var(--color-text);font-size:.75rem;font-weight:700;
}
.a2hs-ico{display:inline-flex;vertical-align:-.2em;margin:0 .1rem;width:1.1rem;height:1.1rem}
.a2hs-ico svg{width:100%;height:100%}
.a2hs-hint{margin:.875rem 0 0;color:var(--color-text);font-size:.8rem;line-height:1.45}
.a2hs-act{display:flex;flex-direction:column;align-items:flex-start;gap:.5rem;margin-top:1.1rem}
.a2hs-off{
  padding:0;background:none;border:0;cursor:pointer;font:inherit;
  color:var(--color-text);font-size:.8rem;text-decoration:underline;
}
.a2hs-off:hover{color:var(--color-heading)}
