
/* Сайт №42 (Michael) - ДВЕ ТЕМЫ, СВЕТЛАЯ ПО УМОЛЧАНИЮ.
   Каталоги, на которые равняемся, все светлые: покупатель сравнивает
   номера деталей, а это легче на светлом. Тёмная осталась - по выбору.

   Разведка №42: цвета были НЕ все через переменные. В :root их было семь,
   а прямо в правилах сидело ещё около двадцати - подложка карточки,
   плашки галереи, полупрозрачный фон шапки, жёлтая заметка о наличии,
   тени, белый текст на значке "Sold". Ровно они и не переключались бы
   вместе с темой, поэтому все переведены в переменные ниже. */
:root {
  --bg: #ffffff;
  --bg-elevated: #f5f6f8;
  --surface-sunken: #e9ebef;
  --border: #ccd1d9;
  /* Сайт №42 - у РАМОК ЭЛЕМЕНТОВ УПРАВЛЕНИЯ (поля ввода, выбор валюты,
     кнопки) отдельный, более тёмный цвет: WCAG 1.4.11 требует для них
     3:1 к фону. Разделительная рамка карточки под это требование не
     подпадает - поверхности и так различаются фоном, а рамка лишь
     украшение; делать её такой же тёмной значило бы утяжелить сетку без
     пользы для кого бы то ни было. */
  --border-strong: #6b7280;
  --text: #14161a;
  --text-muted: #565d66;
  --accent: #14161a;
  --on-accent: #ffffff;
  --card-hover-border: #8b949e;
  --overlay: rgba(255,255,255,0.86);
  --overlay-strong: rgba(255,255,255,0.96);
  --overlay-text: #14161a;
  --header-bg: rgba(255,255,255,0.94);
  --lightbox-bg: rgba(16,18,22,0.94);
  --shadow-soft: rgba(20,22,26,0.14);
  --shadow-strong: rgba(20,22,26,0.22);
  --warn-border: #a97b12;
  --warn-bg: rgba(200, 150, 20, 0.14);
  --warn-text: #7a5600;
  --primary-hover: #0a4fae;
  /* Сайт №28 п.13 - REAL DEFECT found by the axe audit: white text on the
     original #d9534f only reached 3.96:1 (.badge--sold) - below the 4.5:1
     AA floor for normal-size text. Darkened just enough to clear it
     (5.33:1) while staying visually "red".
     Сайт №37 - ЭТОТ КОММЕНТАРИЙ БЫЛ НАПИСАН ЧЕРЕЗ // И УХОДИЛ В САМ CSS.
     Двойная косая - не комментарий CSS: разбор считал строку негодным
     объявлением и пропускал всё до следующей точки с запятой, то есть
     вместе с ней съедал и --danger. Переменная не определялась вовсе,
     background: var(--danger) у значка "Sold" становился недействительным,
     и значок оставался без фона - белый текст на светлой карточке. То
     самое место, про которое страница заявления обещала контраст AA. */
  --danger: #b3261e;
  /* Сайт №28 п.9 - dedicated focus-ring color, distinct from --accent/
     --text so it's unmistakable against either dark background (8.7:1 on
     --bg, 7.9:1 on --bg-elevated). */
  --focus: #0b5fd0;
  --radius: 10px;
  /* Задание Сайт №7 - fallback for browsers with JS disabled/before first
     paint; site.js overwrites this with the header's REAL measured height
     on load/resize, so the sticky sidebar's offset stays correct no matter
     how tall the header renders (long seller name wrapping to two lines,
     different font metrics, etc.) instead of a guessed magic number. */
  --header-h: 60px;
  color-scheme: light;
}

/* Тёмная тема - ровно те цвета, что были у сайта до этой задачи. Класс
   ставится на <html> из site.js и, чтобы не было вспышки чужой темы,
   ещё раньше - встроенным скриптом в <head> (templates.ts), тем же
   способом, что и настройки доступности. */
html.theme-dark {
  --bg: #0b0c0f;
  --bg-elevated: #17181c;
  --surface-sunken: #1c1d22;
  --border: #2a2c33;
  --border-strong: #767c88;
  --text: #e8e9ec;
  --text-muted: #9a9ca6;
  --accent: #e8e9ec;
  --on-accent: #06121f;
  --card-hover-border: #454852;
  --overlay: rgba(11,12,15,0.65);
  --overlay-strong: rgba(11,12,15,0.9);
  --overlay-text: #ffffff;
  --header-bg: rgba(11,12,15,0.92);
  --lightbox-bg: rgba(5,5,7,0.94);
  --shadow-soft: rgba(0,0,0,0.35);
  --shadow-strong: rgba(0,0,0,0.45);
  --warn-border: #8a6d1a;
  --warn-bg: rgba(212, 168, 44, 0.12);
  --warn-text: #d4a82c;
  --danger: #c9302c;
  --focus: #5eb3ff;
  --primary-hover: #7cc1ff;
  color-scheme: dark;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  line-height: 1.5;
}
a { color: inherit; text-decoration: none; }
a:hover { text-decoration: underline; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); }

/* Сайт №28 п.9 - visible focus outline on every interactive element,
   dark-theme-appropriate (default UA outlines are easy to lose against
   var(--bg)). :focus-visible (not plain :focus) so a mouse click doesn't
   draw a ring, only real keyboard/AT focus does. */
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible,
summary:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--focus); outline-offset: 2px;
}

/* Сайт №28 п.9 - "ссылка «к содержимому»" - the very first focusable thing
   on every page (see htmlShell), invisible until it actually receives
   keyboard focus. */
.skip-link {
  position: absolute; top: -100px; left: 12px; z-index: 100;
  background: var(--bg-elevated); color: var(--text); border: 1px solid var(--focus);
  padding: 10px 16px; border-radius: var(--radius); font-size: 0.9rem;
}
.skip-link:focus { top: 12px; }

/* Задание Сайт №33 п.2 - fixed accessibility icon, present on every page
   (see templates.ts's htmlShell). Bottom-right, clear of the sticky header
   (top) and the sticky facet sidebar (which stops short of the viewport's
   right edge on desktop, and is closed by default on a phone - see
   .browse__facets/.facets-toggle-wrap) - z-index kept BELOW the lightbox's
   200 (see .lightbox above) so an open lightbox correctly covers it like
   everything else, never floats on top of a full-screen photo. 44x44 -
   a real touch target, not a decorative sliver, on every screen size
   including a phone's, without needing its own separate mobile override. */
.a11y-fab {
  /* Сайт №40 - размер значка постоянный: 21px здесь, а не 1.3rem, иначе на
     160% он раздувается вместе с текстом. */
  font-size: 21px;
  position: fixed; right: 16px; bottom: 16px; z-index: 50;
  width: 44px; height: 44px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-elevated); border: 1px solid var(--border); color: var(--text);
  text-decoration: none;
  box-shadow: 0 2px 10px var(--shadow-soft);
}
.a11y-fab:hover { border-color: var(--focus); text-decoration: none; }

/* Сайт №37 (Michael) - ПАНЕЛЬ СРЕДСТВ ДОСТУПНОСТИ. Значок раньше был
   ссылкой на страницу заявления и не давал человеку ничего; теперь он
   открывает вот эту панель. Всё состояние держится классами на <html>,
   поэтому любое средство - это чистый CSS, без пересчёта чего-либо в JS. */
.a11y-panel {
  /* Сайт №40 (дополнение Michael) - панель НЕ увеличивается вместе с
     текстом сайта: на 160% она вырастала до 594px и закрывала каталог.
     Базовый размер задан в пикселях, всё внутри - в em от него, поэтому
     панель одинакова на всех пяти шагах увеличения. Ограничение по высоте
     оставлено на случай очень низкого окна. */
  font-size: 16px;
  max-height: calc(100vh - 96px); overflow-y: auto;
  position: fixed; right: 16px; bottom: 70px; z-index: 60;
  width: 260px; max-width: calc(100vw - 32px);
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 10px;
  box-shadow: 0 6px 24px var(--shadow-strong);
  display: flex; flex-direction: column; gap: 6px;
}
.a11y-panel[hidden] { display: none; }
.a11y-panel__head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.a11y-panel__head h2 { margin: 0; font-size: 1em; }
.a11y-panel__close {
  background: none; border: none; color: var(--text); font-size: 1.4em;
  line-height: 1; cursor: pointer; padding: 2px 6px; border-radius: 6px;
}
.a11y-panel__group { display: flex; align-items: center; gap: 8px; }
.a11y-panel__step {
  flex: 0 0 auto; min-width: 44px; min-height: 40px;
  background: var(--bg); color: var(--text); border: 1px solid var(--border);
  border-radius: 8px; font-size: 1em; cursor: pointer;
}
.a11y-panel__size { flex: 1 1 auto; text-align: center; color: var(--text-muted); font-size: 0.9em; }
.a11y-panel__item {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  min-height: 40px; padding: 6px 10px; text-align: start;
  background: var(--bg); color: var(--text); border: 1px solid var(--border);
  border-radius: 8px; font-size: 0.92em; cursor: pointer;
}
.a11y-panel__item[aria-pressed="true"] { border-color: var(--focus); }
/* Состояние переключателя видно НЕ ТОЛЬКО цветом рамки - иначе оно
   пропадает для человека, не различающего цвета (WCAG 1.4.1). */
.a11y-panel__item .a11y-panel__state::after { content: "\2610"; font-size: 1.1em; }
.a11y-panel__item[aria-pressed="true"] .a11y-panel__state::after { content: "\2611"; }
.a11y-panel__reset, .a11y-panel__statement {
  min-height: 40px; display: flex; align-items: center; justify-content: center;
  background: var(--bg); color: var(--text); border: 1px solid var(--border);
  border-radius: 8px; font-size: 0.9em; cursor: pointer; padding: 6px 10px;
}
.a11y-panel__statement { text-decoration: underline; text-underline-offset: 2px; }
.no-js .a11y-fab, .no-js .a11y-panel { display: none; }

/* --- сами средства --- */
/* Размер текста: вся типографика сайта в rem, поэтому корневой размер
   масштабирует именно текст, не ломая раскладку. */
html.a11y-text-1 { font-size: 112.5%; }
html.a11y-text-2 { font-size: 125%; }
html.a11y-text-3 { font-size: 140%; }
html.a11y-text-4 { font-size: 160%; }

/* Высокий контраст: переопределяются те же переменные, которыми окрашен
   весь сайт, поэтому средство действует на каждый элемент разом. */
/* Сайт №42 - высокий контраст теперь есть у ОБЕИХ тем: на светлой это
   чёрное по белому, на тёмной - белое по чёрному, как было. Раньше он
   всегда делал тёмный вариант, и на светлом сайте это выглядело бы как
   самовольное переключение темы. */
html.a11y-contrast {
  --bg: #ffffff;
  --bg-elevated: #ffffff;
  --surface-sunken: #ffffff;
  --border: #000000;
  --border-strong: #000000;
  --text: #000000;
  --text-muted: #000000;
  --accent: #000000;
  --on-accent: #ffffff;
  --card-hover-border: #000000;
  --overlay: rgba(255,255,255,0.96);
  --overlay-strong: #ffffff;
  --overlay-text: #000000;
  --header-bg: #ffffff;
  --warn-border: #000000;
  --warn-bg: #ffffff;
  --warn-text: #000000;
  --danger: #a4000f;
  --focus: #0000cc;
}
html.a11y-contrast.theme-dark {
  --bg: #000000;
  --bg-elevated: #000000;
  --surface-sunken: #000000;
  --border: #ffffff;
  --border-strong: #ffffff;
  --text: #ffffff;
  --text-muted: #ffffff;
  --accent: #ffffff;
  --on-accent: #000000;
  --card-hover-border: #ffffff;
  --overlay: rgba(0,0,0,0.96);
  --overlay-strong: #000000;
  --overlay-text: #ffffff;
  --header-bg: #000000;
  --warn-border: #ffffff;
  --warn-bg: #000000;
  --warn-text: #ffffff;
  --danger: #ff4d4d;
  --focus: #ffff00;
}
html.a11y-contrast img { filter: contrast(1.15); }
html.a11y-contrast .badge, html.a11y-contrast .a11y-panel__item { border: 1px solid var(--border); }

html.a11y-underline a { text-decoration: underline !important; }

/* Крупный курсор: SVG в data-URI, чтобы не тянуть отдельный файл. */
html.a11y-cursor, html.a11y-cursor * {
  cursor: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 0 48 48'%3E%3Cpath d='M6 2 L6 38 L15 30 L21 44 L28 41 L22 27 L34 27 Z' fill='%23ffffff' stroke='%23000000' stroke-width='2'/%3E%3C/svg%3E") 4 2, auto;
}
html.a11y-cursor a, html.a11y-cursor button, html.a11y-cursor summary, html.a11y-cursor [role="button"] {
  cursor: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 0 48 48'%3E%3Cpath d='M18 4 a4 4 0 0 1 8 0 v16 h2 a4 4 0 0 1 4-4 h2 a4 4 0 0 1 4 4 v12 a10 10 0 0 1-10 10 h-8 a10 10 0 0 1-10-10 V16 a4 4 0 0 1 8 0 z' fill='%23ffffff' stroke='%23000000' stroke-width='2'/%3E%3C/svg%3E") 14 2, pointer;
}

/* Остановка движения. На сайте оно одно - поворот шеврона у раскрывающихся
   фильтров (.facets-toggle__chevron), но правило написано на всё сразу,
   чтобы любое движение, добавленное позже, тоже подчинялось этой кнопке. */
html.a11y-motion *, html.a11y-motion *::before, html.a11y-motion *::after {
  animation-duration: 0s !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0s !important;
  scroll-behavior: auto !important;
}

@media (max-width: 520px) {
  .a11y-panel { right: 8px; left: 8px; width: auto; }
}

/* Сайт №28 п.7/п.9 - hidden until site.js confirms JS actually runs (see
   htmlShell's inline no-js-removal script) - a control that does nothing
   without JS must never be visible without it. */
.no-js .product-gallery__nav, .no-js .product-gallery__zoom, .no-js .share-links__copy {
  display: none;
}

/* Сайт №41 (Michael) - ПЛАВАЮЩАЯ КНОПКА СВЯЗИ.
   Стоит РЯДОМ со значком доступности, а не поверх: доступность остаётся в
   самом углу (right: 16px), связь - левее ровно на ширину значка плюс
   отступ (right: 72px). Панели у них общее место над углом, поэтому
   site.js закрывает одну, когда открывается другая. Размер, как и у
   панели доступности, задан в пикселях и не растёт вместе с текстом
   сайта (Сайт №40): это управление, а не содержимое. */
.contact-fab {
  font-size: 21px;
  position: fixed; right: 72px; bottom: 16px; z-index: 50;
  width: 44px; height: 44px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-elevated); border: 1px solid var(--border); color: var(--text);
  cursor: pointer;
  box-shadow: 0 2px 10px rgba(0,0,0,0.35);
}
.contact-fab:hover { border-color: var(--focus); }
/* Сайт №57 п.1 (Michael) - ПАНЕЛЬ ШИРИНОЙ ПО САМОМУ ДЛИННОМУ ПУНКТУ.
   Было жёсткие 240px, и подпись "Email in Gmail (opens in browser)"
   переносилась на вторую строку; по-русски и на иврите подписи ещё длиннее,
   так что подбирать одно число под все языки - гадание. width: max-content
   заставляет панель померить собственное содержимое: она вырастает ровно
   настолько, сколько нужно самому длинному пункту, на любом языке и при
   любом размере шрифта у человека. Панель приколота справа (right: 16px),
   поэтому растёт она влево, как и просил Michael. min-width оставляет
   прежние 240px нижней границей, чтобы короткий список не сжимался в
   лепесток, а max-width не даёт вылезти за экран. */
.contact-panel {
  font-size: 16px;
  position: fixed; right: 16px; bottom: 70px; z-index: 60;
  width: max-content; min-width: 240px; max-width: calc(100vw - 32px);
  max-height: calc(100vh - 96px); overflow-y: auto;
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 10px;
  box-shadow: 0 6px 24px rgba(0,0,0,0.45);
}
.contact-panel[hidden] { display: none; }
.contact-panel__head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 6px; }
.contact-panel__head h2 { margin: 0; font-size: 1em; }
.contact-panel__close {
  background: none; border: none; color: var(--text); font-size: 1.4em;
  line-height: 1; cursor: pointer; padding: 2px 6px; border-radius: 6px;
}
.contact-panel__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.contact-panel__item {
  display: flex; align-items: center; gap: 8px; min-height: 40px; padding: 6px 10px;
  /* Сайт №57 - вместе с width: max-content выше это и есть "каждый пункт в
     одну строку": перенос запрещён, поэтому мерить панели себя приходится
     по неразрывной подписи. */
  white-space: nowrap;
  background: var(--bg); color: var(--text); border: 1px solid var(--border);
  border-radius: 8px; font-size: 0.92em; text-decoration: none;
}
.contact-panel__item:hover { border-color: var(--focus); text-decoration: none; }
/* Значок канала - буква в кружке: без внешних картинок и без выдумывания
   чужих логотипов, которые нельзя ставить без разрешения правообладателя. */
.contact-panel__icon {
  flex: 0 0 auto; width: 24px; height: 24px; border-radius: 50%;
  background: var(--bg-elevated); border: 1px solid var(--border);
  display: inline-flex; align-items: center; justify-content: center; font-size: 0.8em;
}
.contact-panel__icon--whatsapp::after { content: "W"; }
.contact-panel__icon--telegram::after { content: "T"; }
.contact-panel__icon--viber::after { content: "V"; }
.contact-panel__icon--instagram::after { content: "I"; }
.contact-panel__icon--messenger::after { content: "M"; }
/* Сайт №56 - оба способа письма ведут на один адрес, поэтому значок у
   них общий; различает их подпись, а не картинка. */
.contact-panel__icon--email-gmail::after, .contact-panel__icon--email-app::after { content: "@"; }
.contact-panel__icon--phone::after { content: "\260E"; }
.no-js .contact-fab, .no-js .contact-panel { display: none; }
@media (max-width: 520px) {
  .contact-panel { right: 8px; left: 8px; width: auto; }
  /* Сайт №57 - на узком телефоне панель уже растянута во всю ширину экрана,
     шире ей стать некуда. Замерено на 320px: самая длинная подпись
     ("Письмо в Gmail (откроется в браузере)") в одну строку туда физически
     не влезает, и запрет переноса не выигрывает строку, а ОБРЕЗАЕТ хвост
     подписи. Поэтому здесь перенос разрешён обратно: на 375px и шире всё
     равно выходит одна строка (проверено), а на 320px две читаемые строки
     лучше обрубка. */
  .contact-panel__item { white-space: normal; }
}

/* Задание Сайт №39 (Michael) - ЛЕНТА ПЛИТОК КАТЕГОРИЙ.
   Прокрутка вбок делается самим контейнером (overflow-x: auto), поэтому
   палец на телефоне и колесо/трекпад на настольном работают без JS, а
   фокус клавиатурой браузер подкручивает сам. Кнопки-стрелки - добавка
   для мыши, они скрыты, пока site.js не увидит, что лента действительно
   не помещается. */
.tiles-strip { position: relative; margin: 0 0 18px; }
.tiles {
  display: block; list-style: none; margin: 0; padding: 2px 2px 10px;
  /* Сайт №46 - плитки по центру. Сайт №48 п.2 центрировал их полями
     auto у внутренней дорожки .tiles__track.
     Дополнение к Заданию Сайт №82 (Michael) - РЕАЛЬНЫЙ ДЕФЕКТ, найден на
     iPhone: обещание из №48 ("когда не помещается - поля схлопываются в
     ноль") не выполняется. Поля auto делят ПЕРЕПОЛНЕНИЕ пополам: дорожка
     съезжает влево на половину лишней ширины, и эта половина оказывается
     ЗА нулём прокрутки - прокруткой её не достать. На экране 390 точек
     первая плитка была наполовину за краем, подпись разрезана.
     Замерено на живом сайте: дорожка 1223px в окне 359px - левый край
     дорожки стоял на -432px, то есть первые 432 точки ленты были
     недостижимы.
     Здесь центрирование делает уже сама лента, и ключевым словом safe:
     "по центру, но не в ущерб началу". Если браузер safe не понимает, он
     отбрасывает объявление целиком и лента встаёт от начала - то есть
     запасной путь ровно тот, который нужен на телефоне, а не обрезка. */
  display: flex; justify-content: safe center;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x proximity; scroll-behavior: smooth;
  /* Сайт №39 - лента остаётся прокручиваемой и без анимации: правило
     html.a11y-motion (Сайт №37) отменяет плавность, а не саму прокрутку. */
  -webkit-overflow-scrolling: touch;
}
.tiles__track { display: flex; gap: 12px; width: max-content; margin: 0; padding: 0; list-style: none; }
.tiles__item { flex: 0 0 auto; scroll-snap-align: start; }
/* Сайт №40 - ширина плитки в rem: подпись растёт вместе с текстом, и
   плитка растёт вместе с ней, а не сдавливает её в пять строк. */
/* Сайт №41 (Michael) - ПЛИТКА БОЛЬШЕ НЕ ПОХОЖА НА КАРТОЧКУ ТОВАРА.
   Была широким прямоугольником с рамкой и фоном - то же самое, что
   карточка товара, и покупатель их не различал. Стала портретной,
   заметно меньше, без рамки и подложки: только скруглённое фото, под ним
   название обычным текстом, под названием - число. В ряд помещается
   около десяти. Размер по-прежнему в rem (Сайт №40), поэтому растёт
   вместе с текстом. */
.tile {
  /* Задание Сайт №60 п.3 (Michael) - ПОДПИСЬ НЕ ДЛИННЕЕ ДВУХ СТРОК.
     Было 7rem (112px), и подписи ломались на три строки. Ширина взята
     не под названия Michael'а, а по модулю сетки товаров ниже:
     minmax(200px, 1fr), то есть карточка ~200px = 12.5rem. Плитка
     становится с ней в один шаг по ширине, оставаясь другой по форме
     (рамка 11:6 из №51), и подпись получает вдвое больше места.
     Единица - rem, как задано в №40: подпись растёт вместе с текстом
     сайта, и плитка растёт вместе с ней. */
  display: block; width: 12.5rem; text-decoration: none; color: var(--text);
}
/* Сайт №50 - картинка плитки целиком, без обрезки (как на карточках
   товара в Сайте №48): свои картинки под категории обрезка портит.

   Сайт №51 (Michael) - РАМКА ПОДОГНАНА ПОД НАСТОЯЩИЕ КАРТИНКИ. Michael
   сделал свои десять и все они одного размера, 1408x768 - это ровно 11:6
   (1.833). В квадратной рамке из Сайта №50 они заполняли 55% площади и
   висели узкой полосой между пустыми полями. Рамка 11:6 даёт им 100%: ни
   обрезки, ни пустых полей.
   Замерено, а не подобрано на глаз: 4:3 дало бы 73%, 3:2 - 82%,
   16:9 - 97%, и только точное отношение исходников - 100%.
   Плата за это - плитка "Всё остальное", у которой картинка берётся из
   фотографии товара (Сайт №43) и обычно близка к квадрату: в широкой
   рамке она заполнит около половины. Обрезать её нельзя - это то же
   правило, что и для остальных, - поэтому она просто стоит по центру с
   полями по бокам. */
.tile__img {
  display: block; width: 100%; aspect-ratio: 11 / 6; background: var(--surface-sunken);
  border-radius: 14px; overflow: hidden; border: 1px solid var(--border);
}
.tile__img img { display: block; width: 100%; height: 100%; object-fit: contain; }
.tile__noimg { display: block; width: 100%; height: 100%; background: var(--surface-sunken); }
.tile:hover .tile__img { border-color: var(--focus); }
/* Дополнение к Заданию Сайт №82 (Michael) - "плитка текущего раздела должна
   быть отмечена, чтобы было видно, где человек находится". Отмечена двумя
   способами сразу, потому что одного мало: рамкой и подписью потемнее - для
   глаза, атрибутом aria-current="page" в разметке - для программы чтения с
   экрана. Цветом одним обойтись нельзя: у половины отметок этого сайта уже
   есть своё значение (красный - продано), да и различать цвета умеют не
   все. */
.tile--current .tile__img { border-color: var(--focus); border-width: 2px; }
.tile--current .tile__label { font-weight: 600; color: var(--text); }
.tile__text { display: block; padding: 6px 2px 0; }
/* Сайт №60 - ширина сама по себе двух строк не гарантирует: у покупателя
   шаблона названия будут другой длины, и подгонять ширину под них
   нельзя. Обрезка по двум строкам - это и есть гарантия, работающая на
   любых данных. Текст в разметке остаётся целым, прячется только показ,
   поэтому программа чтения с экрана по-прежнему читает название
   полностью. */
.tile__label {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
/* Задание Сайт №87 - overflow-wrap: anywhere знают Safari с 15.4, Chrome с
   80, Firefox с 65. Ниже этих версий объявление отбрасывается, и длинный
   номер детали снова вылезает за край экрана - тот самый дефект, который
   чинили в Сайте №83 и №84. Стоящее перед ним break-word знают все с
   незапамятных времён; оно делает почти то же самое (различие только в
   том, как считается наименьшая ширина коробки), и на старом браузере
   спасает именно от вылезания за край. */
  font-weight: 400; font-size: 0.82rem; line-height: 1.25; overflow-wrap: break-word; overflow-wrap: anywhere;
}
.tile__count { display: block; color: var(--text-muted); font-size: 0.72rem; margin-top: 2px; }
.tiles-strip__arrow {
  position: absolute; top: 60px; z-index: 5;
  width: 40px; height: 40px; border-radius: 50%;
  background: var(--bg-elevated); color: var(--text); border: 1px solid var(--border);
  font-size: 1.4rem; line-height: 1; cursor: pointer;
}
/* Задание Сайт №87 - логические свойства знают Safari с 14.1, Chrome с 87,
   Firefox с 63. Там, где их нет, объявление отбрасывается целиком, и
   стрелка встаёт не туда, где ей место, а куда придётся. Физическая пара
   первой строкой - запас для старых: слева-справа как в европейском
   письме. Новые браузеры возьмут логическую, она ниже; на иврите и
   арабском там всё останется правильным. */
.tiles-strip__arrow--prev { left: -6px; inset-inline-start: -6px; }
.tiles-strip__arrow--next { right: -6px; inset-inline-end: -6px; }
.tiles-strip__arrow[hidden] { display: none; }
.no-js .tiles-strip__arrow { display: none; }
@media (max-width: 700px) {
  .tile { width: 5.5rem; }
  .tiles-strip__arrow { display: none; }
}

/* header */
.site-header {
  /* Задание Сайт №87 - Safari с 6.1 по 12 знает липкое положение только
     как -webkit-sticky. Значение, которого браузер не понимает, он
     отбрасывает целиком, поэтому две строки подряд - обычный приём: старый
     возьмёт первую, новый вторую. Без неё шапка на старом Safari просто
     уезжает вверх при прокрутке. */
  position: -webkit-sticky;
  position: sticky; top: 0; z-index: 20;
  background: var(--header-bg);
  /* Задание Сайт №87 - Safari до 18-й знает это свойство только с
     приставкой. Строка стоит первой, чтобы новые браузеры взяли
     последнюю, обычную. Без неё на Safari 17 и старше размытия нет
     совсем - фон шапки почти непрозрачный, так что беда невелика, но
     строка стоит копейки. */
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--border);
}
.site-header__inner {
  max-width: none; margin: 0 auto; padding: 12px 19px;
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
}
/* Сайт №40 - было white-space: nowrap: при 160% длинное имя продавца
   выдавливало поиск и вылезало за край шапки. Теперь переносится; высоту
   шапки site.js меряет сам (syncHeaderHeight), поэтому липкая колонка
   фильтров остаётся на месте. */
.site-logo { font-weight: 600; font-size: 1.1rem; overflow-wrap: break-word; overflow-wrap: anywhere; }
/* Задание Сайт №69 (Michael) - НАЗВАНИЕ И НОМЕР ОДНОЙ ГРУППОЙ СЛЕВА.
   flex-wrap: wrap внутри самой группы - это и есть ответ на "если не
   помещаются, перенеси номер на вторую строку": на 360 точках номер
   уходит под название сам, без отдельного правила под ширину экрана и
   без единого пикселя наезда. Прятать его нельзя (Сайт №62), и здесь
   нечему прятаться - перенос ничего не убирает.
   column-gap меньше row-gap нарочно: в одну строку они читаются как
   "название, телефон", в две - как две отдельные строки. */
.site-header__brand {
  display: flex; align-items: baseline; flex-wrap: wrap;
  column-gap: 14px; row-gap: 2px;
}
/* Задание Сайт №62 (Michael) - ЧИСЛО ПОЗИЦИЙ И НОМЕР ТЕЛЕФОНА В ШАПКЕ.
   На широком экране блок прижат к правому краю (margin-inline-start: auto).
   На узком - переносится на вторую строку шапки ЦЕЛИКОМ (flex-basis: 100%
   ниже), а не ужимается и не прячется: Michael отдельно оговорил, что номер
   должен быть виден и на телефоне. Родительский .site-header__inner уже
   flex-wrap: wrap, поэтому перенос ему ничего не ломает, а высоту шапки
   site.js меряет сам (syncHeaderHeight), и липкая колонка фильтров остаётся
   на месте. */
.site-header__meta {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin-inline-start: auto; font-size: 0.9rem;
}
/* Задание Сайт №66 - ссылка "О нас" в шапке. Стоит в том же блоке, что
   число позиций и телефон (Сайт №62), и переносится вместе с ним. Вид -
   как у телефона: обычная ссылка, не кнопка; главные действия в шапке -
   поиск и выбор валюты, спорить с ними ей незачем. */
.site-header__about { color: var(--text); white-space: nowrap; text-decoration: underline; text-underline-offset: 2px; }
.site-header__about:hover { text-decoration-thickness: 2px; }
.site-header__count { color: var(--text-muted); white-space: nowrap; }
/* Задание Сайт №74 (Michael) - НОМЕР ЗАМЕТНЕЕ ОБЫЧНОГО ТЕКСТА.
   Цвет - var(--focus). Это и есть основной цвет сайта: им покрашены
   .button--primary, то есть «Написать продавцу» и «Запросить счёт».
   Красный не берём - на этом сайте он значит ошибку (--danger, значок
   «Продано»), и телефон в нём читался бы как предупреждение.
   Размер 1.1rem - как у названия сайта рядом; в rem, поэтому растёт
   вместе с текстом от панели доступности (Сайт №40).
   Вес 700, а не 600: цвет один делает номер заметным на светлой теме
   хуже, чем на тёмной, а вес работает одинаково в обеих. */
.site-header__phone {
  color: var(--focus); font-size: 1.1rem; font-weight: 700;
  white-space: nowrap; text-decoration: none;
}
.site-header__phone:hover { text-decoration: underline; }
@media (max-width: 720px) {
  .site-header__meta { flex-basis: 100%; margin-inline-start: 0; justify-content: space-between; }
  /* Задание Сайт №69 - на узком экране группа названия занимает всю первую
     строку: иначе поиск (min-width: 180px) втискивался бы рядом и выдавливал
     номер за край. */
  .site-header__brand { flex-basis: 100%; }

  /* Сайт №105 (Michael) - ПРИЛИПАЕТ ТОЛЬКО ПОИСК.
     РЕАЛЬНЫЙ ДЕФЕКТ, найден на живом iPhone: к верху приклеивалась вся
     шапка целиком - название, поиск, валюта, тема, телефон, ссылки,
     счётчик товаров. Замерено на стенде при ширине 375: высота 214 точек,
     26% экрана, и всё это ехало вместе с прокруткой, закрывая содержимое.

     КАК ЭТО РАБОТАЕТ. Шапка остаётся липкой целиком, но её верхний край
     уводится ВЫШЕ экрана ровно на ту высоту, что лежит над строкой поиска.
     Прокрутка утягивает эту часть за верхнюю кромку, а всё, что ниже -
     строка поиска - остаётся приклеенным. Высоту меряет site.js
     (syncHeaderStickyOffset) и кладёт в --header-scroll-away: посчитать её
     в стилях нельзя, она зависит от длины названия магазина, от того, на
     сколько строк перенёсся номер, и от увеличения текста в панели
     доступности.

     ПОЧЕМУ НЕ СДЕЛАТЬ ЛИПКИМ САМ .site-search. Липкий элемент держится
     только пока в поле зрения его РОДИТЕЛЬ: поиск уехал бы вверх вместе с
     .site-header__inner, и прилипания не вышло бы вовсе.

     ПОРЯДОК СТРОК. Приклеенным остаётся то, что НИЖЕ отступа, поэтому
     поиск должен быть последней строкой. Разметку не трогаем - порядок
     задан свойством order и только на узком экране; на компьютере строки
     идут как раньше. Цена решения: на узком экране порядок обхода
     клавиатурой (название, поиск, валюта...) отличается от того, что видно
     глазом (название, валюта, ссылки, поиск). Для набора кнопок в шапке
     это допустимо - смысл не зависит от их порядка. */
  .site-header { top: calc(-1 * var(--header-scroll-away, 0px)); }
  .site-header__brand { order: 1; }
  .site-currency { order: 2; }
  .site-theme { order: 3; }
  .site-header__meta { order: 4; }
  .site-search { order: 5; flex-basis: 100%; }
}
/* Задание Сайт №92 (Michael) - "поиск в середину, правый край перестаёт
   пустовать". Поиск и раньше умел растягиваться, но его основа (260px) была
   мала, и на широком экране он оставался узкой полоской у левого края, а
   правый блок уезжал на вторую строку. Основа 22rem вместе с flex-grow: 1
   заставляет поиск занять всю середину, а правый блок встаёт к краю первой
   же строкой. На узком экране ничего не меняется: min-width прежний, и всё
   так же переносится в столбик. */
.site-search { position: relative; flex: 1 1 22rem; min-width: 180px; }
.site-search input {
  width: 100%; padding: 8px 12px; border-radius: var(--radius);
  border: 1px solid var(--border-strong); background: var(--bg-elevated); color: var(--text);
  font-size: 0.95rem;
}
/* Задание Сайт №83 (Michael) - РЕАЛЬНЫЙ ДЕФЕКТ, найден на iPhone: набрал
   "Hp", выпал список - названия обрезаны справа, список шире экрана, и от
   касания весь экран ездил вбок.
   Причина. Строка списка - это flex-коробка "картинка + название", а у
   элемента flex по умолчанию min-width: auto, то есть он не сжимается уже
   своего СОДЕРЖИМОГО. Пока название переносится по пробелам, всё хорошо; но
   в этом каталоге сплошь номера деталей, и неразрывный кусок бывает
   длиннее всей коробки. Замерено на живом каталоге, окно 390: у названия
   "Festo MS6-EM1-1/2,MS6-LFR-1/2-D6-E-R-M-AS,MS6-FRM,..." неразрывный
   кусок в 58 знаков, строка требовала 418 точек в коробке 269 - и
   переполнение уезжало за край экрана.
   Что сделано:
   - min-width: 0 разрешает названию сжиматься до ширины коробки;
   - overflow-wrap: anywhere разрывает неразрывный номер, когда иначе он не
     помещается (по пробелам он по-прежнему переносится в первую очередь);
   - две строки и многоточие: подсказка - это подсказка, а не карточка, и
     список из восьми названий по четыре строки не помещался бы на экран
     телефона. Тот же приём, что у названия на карточке товара и у подписи
     плитки;
   - overflow-x: hidden на самом списке - страховка: даже если однажды
     что-то в него попадёт нерастяжимое, вбок не поедет ни список, ни
     страница за ним. */
.site-search__results {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0;
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--radius);
  max-height: 22.5rem; overflow-y: auto; overflow-x: hidden; z-index: 30;
}
.site-search__results a {
  display: flex; gap: 10px; padding: 8px 10px; align-items: center;
  border-bottom: 1px solid var(--border);
}
.site-search__results a > span {
  min-width: 0;
  overflow-wrap: break-word;
  overflow-wrap: anywhere;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
.site-search__results a:last-child { border-bottom: none; }
/* Сайт №93 - подсветка подсказки, на которой стоят стрелки. Отдельный класс,
   а не :hover: клавиатура и мышь могут указывать на разные строки, и
   человеку важно видеть именно ту, которую откроет Enter. Цвет взят тот же,
   которым выделяются активные элементы в остальной части сайта. */
.site-search__results a.is-active {
  background: var(--surface-sunken);
  /* Одной заливки мало: на светлой теме разница между обычной строкой и
     подсвеченной еле видна (проверено на собранном сайте). Полоса слева
     тем же цветом, которым сайт показывает фокус, читается сразу и не
     сдвигает текст - это тень, а не рамка. */
  box-shadow: inset 3px 0 0 var(--focus);
}
.site-search__results a.is-active > span { color: var(--text); }
/* Картинка не сжимается вместе с названием: она и так 36x36. */
.site-search__results img { flex: 0 0 auto; width: 36px; height: 36px; object-fit: cover; border-radius: 4px; background: var(--surface-sunken); }
.site-search__results .none { padding: 10px; color: var(--text-muted); }
/* Сайт №42 - переключатель темы стоит рядом с выбором валюты, как просил
   Michael. Размер не привязан к увеличению текста не нужно: это обычный
   элемент шапки, он растёт вместе со всем остальным (Сайт №40). */
.site-theme {
  min-height: 32px; padding: 4px 10px; border-radius: 8px;
  background: var(--bg-elevated); color: var(--text); border: 1px solid var(--border-strong);
  font-size: 0.85rem; cursor: pointer; white-space: nowrap;
}
.site-theme:hover { border-color: var(--focus); }
.no-js .site-theme { display: none; }

.site-currency select {
  padding: 7px 10px; border-radius: var(--radius); border: 1px solid var(--border-strong);
  background: var(--bg-elevated); color: var(--text);
}
/* Сайт №27 п.4 - small, quiet, non-legal note about converted prices. */
.site-currency-note {
  max-width: none; margin: 0 auto; padding: 0 19px 8px;
  color: var(--text-muted); font-size: 0.78rem;
}

/* stock freshness notice - Задание Сайт №14 п.4, shown site-wide right
   under the header so a visitor sees it no matter where they land. */
.stock-notice {
  background: var(--bg-elevated); border-bottom: 1px solid var(--border);
  color: var(--text-muted); font-size: 0.82rem; text-align: center; padding: 6px 16px;
}

/* layout */
/* Сайт №42 доп. (Michael) - "по краям много пустого места, сократить
   примерно до половины сантиметра". Полсантиметра - это ~19px, столько и
   ставим (было 20px по бокам, но пустоту создавали не они, а потолок
   ширины 1400px: на широком мониторе он оставлял по 500-600px с каждой
   стороны). Потолок поднят, но не убран: без него каталог растянулся бы
   на весь экран и строка стала бы нечитаемо длинной. На узком экране поля
   уменьшаются отдельным правилом ниже. */
/* Задание Сайт №60 (Michael) - КАТАЛОГ ЗАНИМАЕТ ЭКРАН.
   Замерено на сгенерированном сайте до правки: при окне 2560 потолок 1800
   оставлял 373px слева и 388px справа - это по 1.85 ширины карточки товара
   с каждой стороны, ровно то, на что Michael и жаловался. При 1920 потолок
   почти не мешал (53px и 68px), поэтому по файлу дефект и не был виден.

   В Задании Сайт №42 я писал, что без потолка строка каталога станет
   нечитаемо длинной. На каталоге это неверно, и надо сказать прямо: сетка
   товаров - repeat(auto-fill, minmax(200px, 1fr)), то есть лишняя ширина
   уходит в НОВЫЕ КОЛОНКИ, а ширина текста внутри карточки остаётся той же
   ~200px. Читать длинную строку там просто негде. Довод про длину строки
   верен для сплошного текста - он и остаётся в силе: страница товара и
   заявление о доступности сохраняют прежнюю рамку 1800px, разворачиваются
   только страницы каталога (.site-main--wide, ставится в templates.ts).

   Поля теперь задаёт только padding: 19px - это и есть те самые полсантиметра
   (19px при 96 точках на дюйм = 0.50 см), заданные ещё дополнением к №42. */
.site-main { max-width: 1800px; margin: 0 auto; padding: 20px 19px 60px; min-height: 60vh; }
.site-main--wide { max-width: none; }
/* Задание Сайт №32 ШАГ2 - shown only when the page detects it was opened as
   a local file:// (see site.js's initFileProtocolWarning) - a real, if
   uncommon, notice, so it's visually distinct from ordinary muted text but
   short of --danger's full alarm red (nothing is actually broken on THIS
   page, search/filters just don't work from file://). */
.file-protocol-warning {
  margin: 0 0 16px; padding: 10px 14px; border-radius: var(--radius);
  border: 1px solid var(--warn-border); background: var(--warn-bg); color: var(--warn-text); font-size: 0.9rem;
}
/* Дополнение к Заданию Сайт №83 - в крошках теперь до четырёх уровней, и
   среди них названия категорий и номера деталей. overflow-wrap: anywhere -
   та же страховка, что у списка подсказок поиска: длинный неразрывный
   кусок должен переноситься, а не уезжать за край экрана телефона. */
.breadcrumbs { color: var(--text-muted); font-size: 0.85rem; margin-bottom: 16px; overflow-wrap: break-word; overflow-wrap: anywhere; }
/* Задание Сайт №92 - те же четыре места, что нашлись при разборе Сайта
   №90: крошки, "О нас" и "Отложенное" в шапке, путь категории на странице
   товара и ссылка на заявление внутри панели доступности. Приём тот же, что
   в подвале: постоянное подчёркивание, цвет не трогаем - иначе шапка и
   крошки станут пёстрыми. Последний уровень крошек - это <span>, не ссылка,
   и он остаётся без подчёркивания: разница между "сюда можно вернуться" и
   "вы здесь" видна сразу. */
.breadcrumbs a { text-decoration: underline; text-underline-offset: 2px; }
.breadcrumbs a:hover { text-decoration-thickness: 2px; }
/* Задание Сайт №85 (Michael) - СРОЧНО: "крошки не нажимаются вовсе".
   Разметка была в порядке - ссылки настоящие, с настоящими адресами
   (проверено нажатием: переход со страницы категории на страницу группы
   происходит). Мешало другое, и это два разных дефекта сразу.
   ПЕРВЫЙ, главный. Уровень пути был обычной строчной ссылкой, а строчная
   ссылка РАЗРЫВАЕТСЯ по строкам. Замерено на живом сайте, окно 390,
   страница товара: у уровня "Other Semiconductor & PCB Manufacturing" два
   отдельных куска - 34 точки в конце одной строки и 225 в начале
   следующей, - и середина ссылки приходится в пустоту МЕЖДУ ними.
   document.elementFromPoint в этой середине возвращает не ссылку, а сам
   <nav>. То есть палец, ткнувший в середину названия - ровно туда, куда
   целится человек, - не попадал ни во что.
   inline-block запрещает уровню разрываться: он целиком переезжает на
   следующую строку, и середина у него всегда внутри него самого.
   ВТОРОЙ. Высота ссылки - 18 точек, замерено. Меньше половины от 44,
   которые этот же сайт принял за настоящую цель для пальца (Сайт №33).
   На устройствах с пальцем вместо мыши уровни получают отступы сверху и
   снизу: цель становится 40 точек, а текст остаётся прежнего размера. */
.breadcrumbs a, .breadcrumbs > span { display: inline-block; }

/* browse layout */
.browse { display: flex; gap: 28px; align-items: flex-start; }
/* Задание Сайт №7 - the bug: this box had no height cap of its own, so on
   catalogs with many facet groups it grew taller than the viewport; sticky
   positioning still "worked" (browser sat it at top:76px) but there was no
   way to reach checkboxes past the bottom of the screen without scrolling
   the WHOLE page (which only helped once the sidebar's bottom finally
   caught up with the product grid's bottom). Capping its own height to the
   viewport and giving it its own scrollbar is what makes it behave like a
   real sticky sidebar regardless of how many filters a given seller's
   catalog produces (few, for clothing; many, for electronics).
   Задание Сайт №34 - REAL DEFECT found by Michael: at a real (not
   round-number) window width around 700-1000px, this column was gone
   entirely - not collapsed, not behind the "Filters" toggle, just absent.
   Root cause, confirmed live: the sizing/sticky/scroll rules used to live
   on .browse__facets (the <aside>), whose actual DOM parent is the
   <details class="facets-toggle-wrap"> - and that wrapper used
   display:contents so it wouldn't take up its own flex slot. That combo
   is fine at a wide screen (.browse__facets keeps its fixed 220px), but the
   moment the width:100% override kicks in for the stacked layout below,
   percentage-width resolution through a display:contents parent that's
   ALSO a closed <details> collapses to 0 in this engine - display:block,
   but a 0x-wide box, invisible - and there was no toggle at that width to
   at least surface a rescue button (see the single merged breakpoint
   below). Fix: put the sizing/sticky/scroll on .facets-toggle-wrap itself
   (a real flex item now, never display:contents) - percentage width on an
   ordinary flex item is unambiguous, no browser-engine quirk to hit.
   .browse__facets, now just a plain block inside it, needs no width/sizing
   of its own at all - see Задание Сайт №35 below for what replaced the
   "stay visible regardless of [open]" override that used to sit here. */
.facets-toggle-wrap {
  /* Сайт №40 (Michael) - РЕАЛЬНЫЙ ДЕФЕКТ, найден на живом сайте при 160%:
     ширина колонки была жёстко 220px и не росла вместе с текстом, поэтому
     "Adjustable Power Supplies" превращалось в "Adjustable Power" - строка
     не влезала и уезжала за правый край .facet-scroll (замерено: содержимое
     298px в коробке 205px). В rem та же самая ширина при 100% и растёт
     ровно во столько же раз, во сколько текст. */
  width: 13.75rem; flex-shrink: 0;
  /* Задание Сайт №87 - то же, что у шапки: старый Safari знает только
     -webkit-sticky. */
  position: -webkit-sticky;
  position: sticky;
  top: calc(var(--header-h) + 16px);
  max-height: calc(100vh - var(--header-h) - 32px);
  overflow-y: auto;
  overscroll-behavior: contain;
  display: block;
}
.browse__results { flex: 1; min-width: 0; }
.facets-toggle {
  display: none;
  width: 100%; align-items: center; justify-content: space-between; gap: 8px;
  margin-bottom: 16px; padding: 10px 14px;
  border-radius: var(--radius); border: 1px solid var(--border);
  background: var(--bg-elevated); color: var(--text); font-size: 0.95rem; cursor: pointer;
  list-style: none;
}
.facets-toggle::-webkit-details-marker { display: none; }
.facets-toggle__chevron { transition: transform 0.15s ease; }
.facets-toggle-wrap[open] > .facets-toggle .facets-toggle__chevron { transform: rotate(180deg); }
/* Задание Сайт №35 - REAL DEFECT: Сайт №34 kept this element visible on a
   wide screen by forcing display:block via CSS regardless of whether the
   wrapping <details> had its [open] attribute - but a CLOSED <details>,
   even with its child CSS-forced to display:block, left the <details>
   element's OWN box at height:0 in this engine (confirmed live on
   Michael's real catalog: the homepage's much taller facet list made this
   visible as "the whole column is gone", not just misdrawn). No CSS
   override can fix that - the details element itself needs to genuinely
   be open. templates.ts's renderFacetSidebar now defaults the <details>
   to open in the server HTML; site.js closes it by default on a narrow
   screen once it runs (see initFacetsCollapseOnNarrowScreen). Nothing here
   needs to fight the browser's own [open] handling on ANY screen width
   any more - the narrow-screen media query below still hides this specific
   element when the details is closed, using the browser's own native
   behavior (a more specific selector than any of this), not an override. */
.browse__heading h1 { margin: 0 0 4px; font-size: 1.5rem; }
.browse__intro { color: var(--text-muted); margin: 0 0 20px; }
/* Задание Сайт №61 - выбор порядка стоит справа над товарами: там его
   ищут глазами на любой витрине. На узком экране блок переносится под
   заголовок целиком, а не сжимает подпись со списком. */
.browse__toolbar { display: flex; justify-content: flex-end; flex-wrap: wrap; gap: 8px; margin: 0 0 12px; }
.browse__sort { display: inline-flex; align-items: center; gap: 8px; font-size: 0.9rem; }
.browse__sort-label { color: var(--text-muted); white-space: nowrap; }
.browse__sort-select {
  min-height: 40px; padding: 6px 10px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--bg); color: var(--text);
  font: inherit; font-size: 0.9rem; max-width: 100%;
}
.browse__sort-select:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.no-js .no-js-hidden { display: none; }
.browse__filter-status { color: var(--text-muted); font-size: 0.9rem; margin-bottom: 12px; }
.browse__empty { color: var(--text-muted); padding: 40px 0; text-align: center; }

/* Задание Сайт №31 п.4 - facet-group is now a native <details> (collapsed by
   default - see templates.ts's renderFacetSidebar) so a long facet (Country
   of Manufacture, once expanded) never dominates the sidebar by default;
   the browser's own marker triangle is the open/closed affordance, so no
   custom chevron is needed here the way .facets-toggle above has one. */
.facet-group { margin-bottom: 20px; }
.facet-group summary { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); margin: 0 0 8px; cursor: pointer; }
.facet-group summary:hover { color: var(--text); }
.facet-group[open] summary { margin-bottom: 8px; }
/* Сайт №40 - строка фильтра переносится целиком и никуда не вылезает:
   подпись стала отдельным элементом (templates.ts и site.js), которому
   можно разрешить сжиматься (min-width: 0) и переносить даже сплошное
   длинное слово (overflow-wrap: anywhere). align-items: flex-start - чтобы
   галочка стояла у первой строки, а не посередине трёхстрочной подписи. */
.facet__option { display: flex; align-items: flex-start; gap: 6px; font-size: 0.9rem; padding: 3px 0; cursor: pointer; }
.facet__option input { flex: 0 0 auto; margin-top: 0.2em; }
.facet__label { flex: 1 1 auto; min-width: 0; overflow-wrap: break-word; overflow-wrap: anywhere; }
/* Сайт №40 - высота в rem: сколько строк помещалось при 100%, столько же
   помещается и при 160%, а не втрое меньше. overflow-x: hidden убирает
   боковую прокрутку, которой здесь взяться неоткуда после того, как строка
   научилась переноситься (см. .facet__label ниже). */
.facet-scroll { max-height: 13.75rem; overflow-y: auto; overflow-x: hidden; padding-right: 4px; }
/* Nested "Show N more" disclosure inside a facet group, and the outer "Ещё
   фильтры" group wrapping facets past PRIMARY_FACET_COUNT - both native
   <details> too, same reasoning as .facet-group above. */
.facet-more { margin-top: 4px; }
.facet-more summary { font-size: 0.8rem; color: var(--text-muted); cursor: pointer; }
.facet-more summary:hover { color: var(--text); }
.facets-more { margin-bottom: 16px; border-top: 1px solid var(--border); padding-top: 12px; }
.facets-more > summary { font-size: 0.85rem; cursor: pointer; }
.facets-more > summary:hover { color: var(--text); }
.facet__count { color: var(--text-muted); margin-left: auto; font-size: 0.8rem; flex: 0 0 auto; }
.facet__price-range { display: flex; align-items: center; gap: 6px; }
.facet__price-range input {
  /* Сайт №40 - в rem, иначе на 160% число не помещается в поле. */
  width: 4.6rem; padding: 5px 6px; border-radius: 6px; border: 1px solid var(--border-strong);
  background: var(--bg-elevated); color: var(--text);
}
.button { display: inline-block; padding: 7px 14px; border-radius: var(--radius); border: 1px solid var(--border-strong); background: transparent; color: var(--text); cursor: pointer; font-size: 0.85rem; }
.button--ghost:hover { background: var(--bg-elevated); }
/* Сайт №28 п.1 - the contact CTA's own accent color, not just var(--text)
   on transparent like the plain .button above - this is meant to read as
   THE thing to click on the page. */
.button--primary { background: var(--focus); border-color: var(--focus); color: var(--on-accent); font-weight: 600; }
.button--primary:hover { background: var(--primary-hover); text-decoration: none; }

/* product grid */
.product-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 18px;
}
.product-card {
  display: block; background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: var(--radius); overflow: hidden;
}
.product-card:hover { border-color: var(--card-hover-border); text-decoration: none; }
.product-card__img { position: relative; aspect-ratio: 1 / 1; background: var(--surface-sunken); }
/* Сайт №48 п.1 (Michael) - ФОТО ЦЕЛИКОМ, БЕЗ ОБРЕЗКИ. У промышленной
   детали по краям кадра часто и разъём, и маркировка - обрезка их
   срезала. object-fit: contain вписывает снимок целиком, свободное место
   заполняет фон коробки, а он берётся из переменной темы: на светлой
   светлый, на тёмной тёмный, без белой заплатки на тёмном фоне. Высота
   карточек в ряду не меняется: коробка по-прежнему квадратная
   (aspect-ratio: 1/1), меняется только то, как в неё вписан снимок. */
.product-card__img img { width: 100%; height: 100%; object-fit: contain; display: block; }
.product-card__noimg { display: flex; align-items: center; justify-content: center; height: 100%; color: var(--text-muted); font-size: 0.8rem; }
.product-card__body { padding: 10px 12px 14px; }
/* Сайт №42 часть 2 - на карточке сначала марка, потом номер детали
   крупно, потом остаток названия. Покупатель промышленного оборудования
   ищет по номеру, поэтому номер - самая заметная строка карточки, а не
   кусок обрезанного заголовка. Номер не переносится по слогам и не
   обрезается: он должен читаться целиком (overflow-wrap: anywhere
   разрывает только если иначе не помещается). */
.product-card__brand {
  margin: 0; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--text-muted);
}
.product-card__mpn {
  margin: 1px 0 3px; font-size: 1rem; font-weight: 600; line-height: 1.2;
  overflow-wrap: break-word;
  overflow-wrap: anywhere;
}
/* Сайт №47 часть 2 - у карточки БЕЗ номера название встаёт в свой
   обычный вид: крупнее и обычным цветом. Так оно читается как название, а
   не как номер, которого у товара нет. */
.product-card__title--main { font-size: 0.9rem; color: var(--text); }
.product-card__title {
  margin: 0 0 6px; font-size: 0.82rem; color: var(--text-muted);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.price { font-weight: 600; }
.price--sold { color: var(--text-muted); text-decoration: line-through; }
.price--unknown { color: var(--text-muted); font-weight: 400; }
.qty-available { display: block; margin-top: 4px; font-size: 0.8rem; color: var(--text-muted); }
.badge { display: inline-block; font-size: 0.7rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; padding: 3px 8px; border-radius: 999px; }
/* Сайт №42 - белый на красном одинаково читается в обеих темах, это
   плашка, а не тема; контраст замерен (см. отчёт задачи). */
.badge--sold { position: absolute; top: 8px; left: 8px; background: var(--danger); color: #ffffff; }
.badge--inline { position: static; margin: 4px 0; }

.pagination { display: flex; justify-content: center; align-items: center; gap: 16px; margin-top: 32px; color: var(--text-muted); }
/* Задание Сайт №82 (Michael) - ПРОВАЛ ПЕРЕД ПОДВАЛОМ. РЕАЛЬНЫЙ ДЕФЕКТ.
   Замерено на живом сайте, окно 375x812, страница index-3.html: от строки
   "Previous / Page 3 of 19 / Next" до подвала 152 пикселя пустоты - пятая
   часть экрана телефона.
   Треть этого - вторая, ПУСТАЯ строка листания. В разметке она стоит с
   атрибутом hidden (templates.ts, id="filtered-pagination"), но строка
   выше задаёт ей display:flex, а правило автора всегда сильнее правила
   браузера [hidden]{display:none} - и скрытой она не была НИКОГДА: пустая
   коробка нулевой высоты приносила с собой свои же 32px отступа сверху.
   Та же поломка с другого конца: при включённом фильтре site.js прячет
   ПЕРВУЮ строку листания (restoreStaticView: staticNav.hidden = true), и
   она точно так же не пряталась - проверено на живом сайте, на экране
   стояли обе сразу: "Previous / Page 3 of 19 / Next" от неотфильтрованной
   страницы и "Page 1 of 4 / Next" от отфильтрованной.
   Правило автора той же силы лечит оба случая разом - тот же приём, что
   уже стоит у .lightbox[hidden], .contact-panel[hidden] и .a11y-panel[hidden]. */
.pagination[hidden] { display: none; }

/* product detail */
.product-detail { display: flex; gap: 32px; flex-wrap: wrap; }
.product-gallery { flex: 1 1 380px; min-width: 280px; }
.product-gallery__main { position: relative; aspect-ratio: 1 / 1; background: var(--surface-sunken); border-radius: var(--radius); overflow: hidden; touch-action: pan-y; }
/* Сайт №40 - <picture> по умолчанию строчный, и под ним оставался зазор
   базовой линии: коробка галереи переполнялась на 8px, а с ростом текста
   зазор растёт вместе с межстрочным интервалом. Блочный <picture> убирает
   его целиком. */
.product-gallery__main picture { display: block; height: 100%; }
.product-gallery__main img { display: block; width: 100%; height: 100%; object-fit: contain; }
.product-gallery__thumbs { display: flex; gap: 8px; margin-top: 8px; flex-wrap: wrap; }
.product-gallery__thumb { display: inline-block; width: 60px; height: 60px; border-radius: 6px; overflow: hidden; cursor: pointer; opacity: 0.6; border: 1px solid var(--border); }
.product-gallery__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.product-gallery__thumb.is-active, .product-gallery__thumb:hover { opacity: 1; border-color: var(--accent); }
.product-gallery--empty { display: flex; align-items: center; justify-content: center; aspect-ratio: 1/1; background: var(--surface-sunken); border-radius: var(--radius); color: var(--text-muted); }

/* Сайт №28 п.7 - prev/next arrows overlaid on the main photo, and a
   separate zoom button (see templates.ts's own comment on why it's not the
   image itself wrapped in a button). All hidden under .no-js above. */
.product-gallery__nav {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
  width: 36px; height: 36px; border-radius: 50%; border: none;
  background: var(--overlay); color: var(--overlay-text); font-size: 1.4rem; line-height: 1; cursor: pointer;
}
.product-gallery__nav:hover { background: var(--overlay-strong); }
.product-gallery__nav--prev { left: 10px; }
.product-gallery__nav--next { right: 10px; }
.product-gallery__zoom {
  position: absolute; bottom: 10px; right: 10px; z-index: 2;
  width: 34px; height: 34px; border-radius: 50%; border: none;
  background: var(--overlay); color: var(--overlay-text); font-size: 1rem; cursor: pointer;
}
.product-gallery__zoom:hover { background: var(--overlay-strong); }

/* Сайт №28 п.7/п.13 - full-screen lightbox. REAL DEFECT found by manual
   keyboard-nav testing: .lightbox { display: flex } is an AUTHOR rule, and
   author rules always beat the UA stylesheet's [hidden] { display: none }
   regardless of selector specificity - the lightbox was rendering as a
   full-viewport opaque overlay on EVERY page load, hidden attribute or not,
   silently sitting on top of (and stealing Tab focus into) the whole page.
   The [hidden] selector here is an AUTHOR rule of equal specificity that
   restores the expected behavior; site.js only ever toggles the attribute. */
.lightbox {
  position: fixed; inset: 0; z-index: 200;
  background: var(--lightbox-bg);
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.lightbox[hidden] { display: none; }
/* Сайт №29 п.6 - REAL DEFECT: max-width/max-height:100% on an <img> with no
   explicit size can only ever SHRINK it down from its own natural pixel
   size - it never enlarges a small source past that, so even the correct
   full-size tier (680px) rendered nowhere near "fills the screen". Setting
   an explicit height (viewport-relative, not a max-*) forces real upscale-
   or-downscale to that height; width:auto lets the browser compute the
   matching width from the photo's own aspect ratio, and max-width guards
   the rare very-wide photo so it still fits horizontally alongside the
   arrows - browsers correctly re-derive height from THAT cap too (CSS2.1
   replaced-element sizing), so a panoramic photo never overflows either
   way. object-fit: contain on top is the final guarantee against distortion. */
.lightbox__row {
  display: flex; align-items: center; gap: 16px;
  max-width: 100%; max-height: 100%;
}
.lightbox img {
  display: block;
  height: 88vh; width: auto;
  max-width: calc(100vw - 160px); max-height: 88vh;
  object-fit: contain;
}
.lightbox__close {
  position: absolute; top: 16px; right: 16px; z-index: 1;
  width: 40px; height: 40px; border-radius: 50%; border: 1px solid var(--border);
  background: var(--bg-elevated); color: var(--text); font-size: 1.1rem; cursor: pointer;
}
/* Сайт №29 п.6 - REAL DEFECT: these used to be position:absolute against
   the whole viewport's corners - for a photo that renders much narrower
   than the window (a vertical photo especially, or any photo on a wide
   screen) the arrows sat far out at the window edges, nowhere near the
   actual image. Now plain flex siblings of the picture in .lightbox__row
   above - flexbox lays them out right against whatever width the image
   actually rendered at, for either orientation, with no JS measurement
   needed. */
.lightbox__nav {
  flex-shrink: 0;
  width: 48px; height: 48px; border-radius: 50%; border: 1px solid var(--border);
  background: var(--bg-elevated); color: var(--text); font-size: 1.6rem; line-height: 1; cursor: pointer;
}
@media (max-width: 640px) {
  .lightbox { padding: 10px; }
  .lightbox__row { gap: 8px; }
  .lightbox img { height: 80vh; max-width: calc(100vw - 96px); max-height: 80vh; }
  .lightbox__nav { width: 40px; height: 40px; }
}
.product-detail__info { flex: 1 1 380px; min-width: 280px; }
.product-detail__info h1 { margin: 0 0 10px; font-size: 1.4rem; }
.product-detail__info .price { font-size: 1.3rem; display: block; margin-bottom: 16px; }

/* Сайт №28 п.1 - the contact CTA, right after price. Primary button is a
   real, big, obvious click target; the other filled channels sit next to
   it as small links - visible, never hidden behind a menu. */
.contact-cta { margin-bottom: 20px; }
.contact-cta__main { font-size: 0.95rem; padding: 10px 20px; }
.contact-cta__alt { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; font-size: 0.82rem; }
/* Сайт №29 п.2 - REAL DEFECT: these used to be var(--text-muted) with no
   underline - indistinguishable from plain text until hovered. Small
   bordered chip + icon, same visual language as .button--ghost, makes
   "this is clickable" obvious without needing a hover to discover it. */
.contact-cta__alt-link {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 5px 10px; border-radius: 999px; border: 1px solid var(--border);
  color: var(--text);
}
.contact-cta__alt-link:hover { background: var(--bg-elevated); text-decoration: none; }

/* Сайт №28 п.8 - строка текстовых ссылок без иконок и скриптов - УБРАНА
   Заданием Сайт №67 п.2: именно из-за неё блок и не находили. Правила
   больше нет целиком, а не перекрыто новым: два набора правил на одни и
   те же классы расходятся при первой же правке. Новый вид - ниже,
   .share-links рядом с .pay-*. */
.product-detail__meta { margin: 0 0 20px; }
.product-detail__meta div { display: flex; gap: 8px; padding: 4px 0; border-bottom: 1px solid var(--border); font-size: 0.9rem; }
.product-detail__meta dt { color: var(--text-muted); width: 130px; flex-shrink: 0; }
.product-detail__meta dd { margin: 0; }
.product-detail__description p { margin: 0 0 10px; color: var(--text); }
.product-detail__description--empty { color: var(--text-muted); font-style: italic; }
.product-detail__specs { width: 100%; border-collapse: collapse; margin-top: 20px; font-size: 0.88rem; }
.product-detail__specs th, .product-detail__specs td { text-align: left; padding: 6px 10px; border-bottom: 1px solid var(--border); }
.product-detail__specs th { color: var(--text-muted); font-weight: 500; width: 40%; }

/* Задание Сайт №21 - per-product shipping policy section */
/* Сайт №47 - ПРОПУЩЕНО В САЙТЕ №42: два блока (доставка и оплата) остались
   с жёстко вписанным тёмным фоном и на светлой теме выглядели чёрными
   плитами посреди белой страницы. Мой перечень цветов в №42 их не поймал:
   я искал по вхождениям в правилах, а эти два стоят в одну строку вместе с
   рамкой и отступами, и глаз их пропустил. Теперь та же переменная, что у
   остальных приподнятых поверхностей. */
.product-detail__shipping { margin: 0 0 20px; padding: 12px 16px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg-elevated); }
.product-detail__shipping h2 { margin: 0 0 8px; font-size: 1rem; }
.shipping-info__zones { list-style: none; margin: 0 0 6px; padding: 0; font-size: 0.9rem; }
.shipping-info__zones li { padding: 2px 0; }
.shipping-info__zone-label { color: var(--text-muted); margin-right: 4px; }
.shipping-info__custom-quote, .shipping-info__unset { margin: 0 0 6px; font-size: 0.9rem; color: var(--text-muted); }
.shipping-info__pickup { margin: 0 0 6px; font-size: 0.9rem; }
/* Сайт №28 п.4 - was a full comma-joined country list in small pale text;
   now a native <details> disclosure - the summary line is normal-weight
   text (readable), the full list only appears once opened. */
.shipping-info__excluded { margin: 0 0 6px; font-size: 0.85rem; }
.shipping-info__excluded summary { cursor: pointer; color: var(--text-muted); }
.shipping-info__excluded summary:hover { color: var(--text); }
.shipping-info__excluded p { margin: 6px 0 0; color: var(--text-muted); }
.shipping-info__disclaimer { margin: 0; font-size: 0.78rem; color: var(--text-muted); font-style: italic; }

/* Сайт №28 п.3 - category shown as linked levels, same look as .breadcrumbs. */
.category-path { color: var(--text-muted); }
.category-path a { text-decoration: underline; text-underline-offset: 2px; }
.category-path a:hover { text-decoration-thickness: 2px; }

/* Сайт №28 п.10 + Сайт №29 п.8, merged into one block by Задание №162 -
   was two separate boxed cards stacked in a row (payment-method text, then
   an unlabeled total+button box right under it). */
.product-detail__payment { margin: 0 0 20px; padding: 12px 16px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg-elevated); }
.product-detail__payment h2 { margin: 0 0 10px; font-size: 1rem; }
.product-detail__payment p { margin: 0 0 8px; font-size: 0.9rem; }
.product-detail__payment > :last-child { margin-bottom: 0; }
.product-detail__total-line { font-size: 1.05rem; }
.product-detail__total-line .price { font-weight: 600; }
.pay-button { display: inline-block; margin: 0 0 8px; }
/* Сайт №55 - кнопка "скопировать запрос" стоит рядом с кнопкой счёта.
   Без JS буфер обмена недоступен, поэтому кнопка спрятана - то же
   правило, что у стрелок ленты и панели доступности. */
.pay-copy { margin-inline-start: 8px; }
.no-js .pay-copy { display: none; }
/* Задание Сайт №67 п.2 - блок "Поделиться". Обводка и приглушённый фон,
   а не сплошная заливка: главное действие на странице - запрос счёта
   (.pay-button, .button--primary), и спорить с ним этот блок не должен.
   Разница ровно та же, что между .button--primary и .button--ghost во
   всём остальном интерфейсе. */
.share-links {
  margin: 20px 0; padding: 12px 14px;
  border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface-sunken);
}
.share-links__label { display: block; margin-bottom: 8px; font-weight: 600; font-size: 0.9rem; }
.share-links__row { display: flex; flex-wrap: wrap; gap: 8px; }
/* Сайт №105 (Michael) - «три кнопки в строке, Copy link одна во второй -
   выглядит как ошибка».
   ЗАМЕРЕНО на стенде при ширине 375: кнопки 88+81+58+82 точек плюс три
   промежутка по 8 = 333 точки в коробке шириной 307. Не помещаются, и
   последняя уезжает вниз одна.
   Вместо переноса - сетка в два столбца: четыре кнопки встают ровным
   квадратом, и лишней строки с одинокой кнопкой не бывает НИ ПРИ КАКОЙ
   ширине и ни на каком языке. Это важнее, чем кажется: по-русски подпись
   «Скопировать ссылку» вдвое длиннее английской, и любое правило,
   завязанное на ширину кнопки, на другом языке снова дало бы сироту.
   Спрятанный пункт письма (gmail-desktop-only или contact-touch-only)
   ячейку не занимает - display: none в сетке места не отнимает, поэтому
   видимых кнопок ровно четыре и на телефоне, и на компьютере.
   На широком экране правило не действует: там строка как была. */
@media (max-width: 720px) {
  .share-links__row { display: grid; grid-template-columns: 1fr 1fr; }
  .share-links__btn { text-align: center; }
}
.share-links__btn {
  display: inline-block; padding: 7px 12px; border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--bg-elevated);
  color: var(--text); text-decoration: none; font-size: 0.85rem; cursor: pointer;
  font-family: inherit; line-height: 1.2;
}
.share-links__btn:hover { border-color: var(--focus); }
/* Кнопка копирования без JS ничего не делает - правило для неё уже стоит
   выше, вместе со стрелками галереи (Сайт №28 п.7), второй раз не нужно. */
.share-links__copy[data-copied="true"] { border-color: var(--focus); }

/* Сайт №56 - запасные способы письма под кнопкой счёта. Не кнопки:
   главное действие на странице одно, а это выбор для тех, кому оно не
   подходит - вид должен об этом говорить. */
/* Задание Сайт №90 (Michael) - "видно только при наведении мышью, а на
   телефоне наводить нечем". По всему сайту у ссылок снята подчёркивание
   (правило "a { text-decoration: none }" вверху), и подчёркивание
   возвращается только на :hover - для пальца это то же самое, что его нет
   вовсе.
   ЧТО ВЫБРАНО И ПОЧЕМУ. Цвет ссылки плюс постоянное подчёркивание, но НЕ
   вид кнопки. Кнопка запроса счёта рядом - главная и единственная залитая;
   сделать эти два пункта кнопками значило бы поставить рядом три кнопки и
   отнять у главной её место. Ровно тот же выбор, что в Сайте №67 с блоком
   "Поделиться": второстепенное действие остаётся второстепенным на вид.
   Подчёркивание, а не один цвет: цвет в одиночку не читают те, кто плохо
   различает цвета, - для них подчёркивание и есть признак ссылки. */
.pay-alt { font-size: 0.82rem; color: var(--text-muted); margin: 0 0 8px; }
.pay-alt__link {
  margin-inline-end: 12px;
  color: var(--focus);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.pay-note { font-size: 0.78rem; color: var(--text-muted); }
.product-detail__payment-method { color: var(--text-muted); }

/* Задание Сайт №70 - значок "отложить" и страница отложенного.

   Кнопка лежит поверх угла картинки, но НЕ внутри карточки-ссылки (см.
   templates.ts, favoriteButtonHtml): в разметке это сосед карточки, а
   обёртка .product-card-wrap даёт им общую систему координат.

   inset-inline-end, а не right: на иврите и арабском угол должен стать
   левым сам, без отдельного правила.

   .product-card-wrap > .product-card { height: 100% } - обёртка забрала
   себе роль ячейки сетки, и без этого карточки в ряду перестали бы быть
   одной высоты (ячейка растягивается, а блок внутри - нет). */
.product-card-wrap { position: relative; display: block; }
.product-card-wrap > .product-card { height: 100%; }
.fav-btn {
  /* Задание Сайт №87 - физический запас для старых браузеров, см.
     .tiles-strip__arrow выше. */
  position: absolute; z-index: 2; top: 8px; right: 8px; inset-inline-end: 8px;
  width: 34px; height: 34px; padding: 0; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--border-strong); border-radius: 999px;
  background: var(--overlay-strong); color: var(--text);
  font-size: 1.1rem; cursor: pointer;
}
.fav-btn:hover { border-color: var(--focus); }
/* Отложенное - тем же основным цветом, что и остальные главные действия
   сайта (Сайт №74), чтобы отметка читалась как "выбрано мной", а не как
   ошибка. */
.fav-btn[aria-pressed="true"] { color: var(--focus); border-color: var(--focus); }
/* Задание Сайт №70 п.4 - без JavaScript и без работающего хранилища
   значков нет вовсе. Класс no-js снимает site.js до первой отрисовки,
   класс no-fav он же ставит, если проверка записи в хранилище не прошла. */
.no-js .fav-btn, .no-js .site-header__fav,
.no-fav .fav-btn, .no-fav .site-header__fav { display: none; }
.site-header__fav { color: var(--text); white-space: nowrap; text-decoration: underline; text-underline-offset: 2px; }
.site-header__fav:hover { text-decoration-thickness: 2px; }
.site-header__fav-count {
  display: inline-block; margin-inline-start: 6px; padding: 1px 7px;
  border-radius: 999px; background: var(--focus); color: var(--on-accent);
  font-size: 0.78rem; font-weight: 600;
}
.site-header__fav-count[hidden] { display: none; }
/* Страница товара: строка цены. Задание Сайт №85 - значок "отложить"
   отсюда убран, он стал отдельной кнопкой с подписью ниже. */
.product-detail__price-row { display: flex; align-items: center; gap: 12px; }
/* Задание Сайт №85 - "отложить" на странице товара: обычная кнопка с
   сердцем и подписью, ростом с прочие кнопки страницы, а не кружок 34
   точки. Стоит своей строкой под связью с продавцом. */
.product-detail__save { margin-top: 14px; }
.fav-btn--labelled {
  position: static; width: auto; height: auto; min-height: 40px;
  gap: 8px; padding: 8px 14px; border-radius: var(--radius);
  font-size: 0.9rem; background: var(--bg-elevated);
}
.fav-btn__text { font-size: 0.9rem; line-height: 1; }
.favorites { min-height: 200px; }
.favorites h1 { margin: 0 0 16px; font-size: 1.5rem; }
.favorites__empty { color: var(--text-muted); max-width: 560px; line-height: 1.6; }
.favorites__empty[hidden] { display: none; }

/* Задание Сайт №81 - страница "не найдено". Та же колонка, что у прочих
   текстовых страниц, и заметная кнопка обратно в каталог: человек попал
   сюда по чужой ссылке и не должен упереться в тупик. */
.notfound { max-width: 620px; }
.notfound h1 { margin: 0 0 14px; font-size: 1.5rem; }
.notfound p { margin: 0 0 16px; line-height: 1.6; }

/* Задание Сайт №72 - страница политики приватности. Та же колонка, что у
   "О нас" и заявления о доступности: сплошной текст, читать его во всю
   ширину экрана невозможно. */
.legal-page { max-width: 720px; }
.legal-page h1 { margin: 0 0 16px; font-size: 1.5rem; }
.legal-page h2 { margin: 26px 0 8px; font-size: 1.05rem; }
.legal-page p { margin: 0 0 12px; line-height: 1.6; }
.legal-page__contact { list-style: none; margin: 8px 0 0; padding: 0; }
.legal-page__contact li { margin: 0 0 4px; }
.legal-page__updated { margin-top: 28px; color: var(--text-muted); font-size: 0.85rem; }

/* Задание Сайт №66 - страница "О нас". Ширина колонки та же, что у
   заявления о доступности: это страница сплошного текста, и читать её
   во всю ширину экрана невозможно. */
.about-page { max-width: 720px; }
.about-page h1 { margin: 0 0 16px; font-size: 1.5rem; }
.about-page p { margin: 0 0 14px; line-height: 1.6; }

/* Задание Сайт №67 п.3 - отметка "новое поступление". Тот же угол
   карточки и тот же размер, что у отметки "Продано", но другой цвет:
   одна говорит "поздно", другая "свежее" - спутать их нельзя. Зелёный
   взят достаточно тёмным, чтобы белый текст на нём проходил контраст
   AA, который обещает страница заявления о доступности. */
.badge--new { background: #15803d; color: #fff; }

/* Задание Сайт №67 п.1 - блок похожих товаров под страницей товара.
   Ряд из четырёх карточек: minmax(180px, 1fr) на четыре колонки - это
   ровно то, что помещается в один ряд на обычном широком экране, не
   растягивая страницу. На узком колонки схлопываются сами. */
.similar { margin-top: 48px; border-top: 1px solid var(--border); padding-top: 24px; }
.similar__title { margin: 0 0 16px; font-size: 1.1rem; }
.similar__grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; }
@media (max-width: 860px) { .similar__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* Сайт №28 п.11 - accessibility statement page. */
.accessibility-statement { max-width: 720px; }
.accessibility-statement h1 { margin: 0 0 16px; font-size: 1.5rem; }
.accessibility-statement h2 { margin: 24px 0 10px; font-size: 1.1rem; }
.accessibility-statement ul { margin: 0; padding-left: 22px; }
.accessibility-statement li { margin: 0 0 8px; }
[dir="rtl"] .accessibility-statement ul { padding-left: 0; padding-right: 22px; }
.accessibility-contact { list-style: none; margin: 8px 0 0; padding: 0; }
.accessibility-contact li { margin: 0 0 4px; }
.accessibility-statement__updated { margin-top: 24px; color: var(--text-muted); font-size: 0.85rem; }

/* footer */
.site-footer { border-top: 1px solid var(--border); margin-top: 60px; }
/* Задание Сайт №33 п.2 - REAL DEFECT found live: scrolled all the way down,
   the fixed accessibility icon (.a11y-fab, bottom:16px, 44px tall) sat
   directly on top of this footer's own "Accessibility statement" line
   (Сайт №28 п.12) - confirmed via getBoundingClientRect at 375px width,
   genuine overlap, not just close. Extra bottom padding (44 + 16 fab + 20
   clearance) keeps the footer's last line above the icon's reserved strip
   at the bottom of the viewport, on every screen width - the icon itself
   is width-independent (see .a11y-fab), so this isn't a phone-only fix. */
/* Сайт №62 - описание продавца и строка года. Ширина текста ограничена
   отдельно: подвал теперь во всю ширину экрана (Сайт №60), и абзац на 2500px
   читать невозможно - здесь как раз тот сплошной текст, ради которого
   ограничение и существует. */
.site-footer__about { max-width: 70ch; margin: 0 0 8px; }
.site-footer__shipping { max-width: 70ch; }
.site-footer__copyright { margin: 8px 0 0; }
.site-footer__inner { max-width: none; margin: 0 auto; padding: 24px 20px 80px; color: var(--text-muted); font-size: 0.85rem; }
.site-footer__shipping { margin: 0 0 10px; }
/* Задание Сайт №92 (Michael) - ПОДВАЛ В ТРИ КОЛОНКИ.
   auto-fit с minmax даёт ровно то, что просило задание: на широком экране
   три колонки в ряд, на узком они складываются одна под другую сами, без
   отдельного媒 запроса и без сжатия в нечитаемые столбики. 13rem - ширина,
   при которой самая длинная строка ("Заявление о доступности", "Privacy
   policy") ещё помещается в одну-две строки. */
.site-footer__cols {
  display: grid;
  /* Верхняя граница колонки - 22rem: подвал во всю ширину экрана (Сайт
     №60), и без неё на 1920 три колонки растягивались по 600 точек каждая,
     а в них по три коротких ссылки. Ширина ограничена, ряд прижат к началу
     - список читается как список, а не как три пустых поля. */
  grid-template-columns: repeat(auto-fit, minmax(13rem, 22rem));
  gap: 20px 32px;
  margin-bottom: 20px;
}
.site-footer__col-title {
  margin: 0 0 8px;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
}
.site-footer__col-list { list-style: none; margin: 0; padding: 0; }
.site-footer__col-list li { margin-bottom: 6px; }
/* Задание Сайт №92 - нижняя полоса: доставка, дата сборки и год. Это не
   ссылки и не раздел, поэтому они стоят под колонками, отделённые чертой, -
   так они не мешают читать список ссылок и не выглядят его продолжением. */
.site-footer__bottom {
  border-top: 1px solid var(--border);
  padding-top: 14px;
}
.site-footer__links { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 10px; }
/* Дополнение к Заданию Сайт №90 (Michael) - "в подвале ровно то же самое...
   учти, что подвал не должен стать пёстрым, там шесть ссылок подряд".
   Поэтому здесь ТОЛЬКО подчёркивание, без смены цвета: все шесть остаются
   того же приглушённого цвета, что и весь подвал, и от строки про доставку
   рядом отличаются не цветом, а подчёркиванием. Шесть цветных ссылок
   подряд и были бы той самой пестротой; шесть подчёркнутых читаются как
   один список ссылок.
   Контраст при этом не меняется вовсе: подчёркивание не трогает ни цвет
   текста, ни цвет фона (замеры - в отчёте задания). */
.site-footer__col-list a,
.site-footer__links a,
.site-footer__about-link,
.site-footer__a11y {
  text-decoration: underline;
  text-underline-offset: 2px;
}
/* Наведение и клавиатура остаются отличимыми: там подчёркивание ещё и
   утолщается, а цвет становится основным - то есть "здесь я сейчас". */
.site-footer__col-list a:hover,
.site-footer__links a:hover,
.site-footer__about-link:hover,
.site-footer__a11y:hover {
  color: var(--text);
  text-decoration-thickness: 2px;
}
/* Сайт №28 п.9 - REAL DEFECT: opacity: 0.7 on top of var(--text-muted) blended
   this line down to ~4.0:1 against --bg - below the 4.5:1 AA floor for
   normal text (var(--text-muted) alone is already ~6.5-7:1, no dimming
   needed to read as "muted"). */
.site-footer__generated { margin: 0; }
/* Сайт №28 п.12 - "скромная отметка", not a banner - same size/weight as
   the other footer lines, just its own row with an icon. */
/* Задание Сайт №66 - та же ссылка в подвале, рядом со ссылкой на
   заявление о доступности и в том же скромном виде. */
/* Задание Сайт №72 - ссылок в подвале стало две («О нас» и политика
   приватности), и без отступа они слипались в одно слово. */
.site-footer__about-link { display: inline-block; margin-top: 10px; margin-inline-end: 14px; }
.site-footer__about-link:hover { text-decoration: underline; }
.site-footer__a11y { display: flex; align-items: center; gap: 6px; margin-top: 10px; }
.site-footer__a11y:hover { text-decoration: underline; }
.site-footer__a11y-icon { font-size: 1rem; }

/* Задание Сайт №34 - was TWO separate thresholds (860 for "stack the
   layout", 640 for "show the Filters toggle") that didn't correspond to
   the same real screen - the gap between them (a real window anywhere
   from ~641 to ~860px wide, which a scaled-down laptop window lands in
   often - see the fix comment above) is exactly where the sidebar's width
   collapsed to 0 with no toggle button to fall back on. One threshold now:
   at or below it, the layout stacks AND the toggle takes over in the same
   breath, so there is never a width where the sidebar is stacked but not
   toggle-able. A sticky column has no room on a narrow screen anyway (it
   would eat the whole viewport), so below this width it collapses behind
   the "Filters" toggle - Задание Сайт №35 - closed by default is now a
   site.js job (initFacetsCollapseOnNarrowScreen, run once at page load),
   since the <details> defaults to open in the server HTML (see
   templates.ts's renderFacetSidebar and its own comment for why); once
   closed, the browser's own native details[open] handling hides
   .browse__facets with no CSS override needed at all.
   1024px is not a round marketing number - it's the iPad's own landscape
   width, and also the effective CSS width of a 1280px-wide window at
   Windows' own suggested 125% scaling for 1080p/1200p screens (the same
   kind of "narrower than it looks" window Michael described - 1300
   becoming ~1040). Both are real, common window widths, not a guess. */
@media (max-width: 1024px) {
  .browse { flex-direction: column; }
  .facets-toggle-wrap {
    width: 100%; position: static; top: auto;
    max-height: none; overflow: visible;
  }
  .facets-toggle { display: flex; }
  /* Задание Сайт №35 - REAL DEFECT found live: this project's own comments
     (and MDN) say a closed <details>' non-summary children are hidden by
     the browser's OWN stylesheet - confirmed FALSE in the engine this was
     tested in: a closed <details> with zero author CSS left its child at
     display:block, full size, fully visible, verified with getBoundingClientRect
     before/after toggling [open] with no other change. Never trust that
     native behavior sight unseen again - this explicit rule is the ONLY
     thing that actually hides .browse__facets when closed, on any engine
     that behaves like the one tested here. Safe in a way the removed
     Сайт №34 override wasn't: this only ever HIDES a closed <details>'
     child, never tries to force-show one, so it can't hit the zero-size-
     box failure mode that bit Сайт №34 and №35's own first fix attempt. */
  .facets-toggle-wrap:not([open]) > .browse__facets { display: none; }
  .browse__facets { margin-bottom: 20px; }

  /* Дополнение к Заданию Сайт №82 (Michael) - ПОРЯДОК БЛОКОВ НА УЗКОМ
     ЭКРАНЕ. Было: "Фильтры" впереди всего, выше хлебных крошек и заголовка
     раздела, - потому что в разметке колонка фильтров идёт первой (на
     широком экране она левая колонка, и это правильно), а при переносе в
     столбец "левая" превращается в "верхнюю".
     Стало то, что просил Michael: сначала где мы (крошки, заголовок,
     описание), потом фильтры, потом порядок и товары.
     display: contents убирает у .browse__results собственную коробку, и её
     содержимое становится прямыми детьми того же столбца - только так
     фильтры могут встать МЕЖДУ заголовком и товарами, оставаясь в разметке
     там, где они нужны широкому экрану. Разметку при этом не трогаем: одна
     и та же страница обслуживает оба случая. */
  .browse__results { display: contents; }
  /* Задание Сайт №84 (Michael) - РЕАЛЬНЫЙ ДЕФЕКТ, внесённый Сайтом №82:
     "строка переключения страниц прижата влево".
     Замерено на живом сайте, окно 390, страница index-3.html: сетка товаров
     занимала 352 точки от края 19, а строка листания - 249 точек от того же
     края 19. То есть коробка строки была уже колонки и стояла слева, а
     justify-content: center внутри неё центрировал содержимое по её
     собственной ширине - по центру страницы оно от этого не вставало.
     Причина: у .browse стоит align-items: flex-start (для широкого экрана,
     где это две колонки и колонка фильтров не должна тянуться во всю
     высоту). Сайт №82 сделал .browse__results прозрачным (display:
     contents), и его содержимое стало прямыми детьми той же колонки - а в
     колонке flex-start означает уже "прижать к левому краю", а не "к
     верхнему". До №82 эти блоки лежали в обычном блочном потоке и занимали
     всю ширину сами собой.
     stretch возвращает им ровно то поведение: каждый блок во всю ширину
     колонки, и центрирование внутри строки листания снова означает "по
     центру страницы". */
  .browse { align-items: stretch; }
  .browse__results > * { order: 5; }
  .breadcrumbs { order: 1; }
  .browse__heading { order: 2; }
  .facets-toggle-wrap { order: 3; }
  .browse__toolbar { order: 4; }
}
/* product photo/info layout stacks separately - a different pair (gallery
   + specs), not the sidebar/grid pair above, so it keeps its own threshold. */
@media (max-width: 860px) {
  .product-detail { flex-direction: column; }
  /* Задание Сайт №89 (Michael) - РЕАЛЬНЫЙ ДЕФЕКТ, найден набором проверок
     Сайта №88 на первом же прогоне: страница товара БЕЗ ФОТОГРАФИЙ на
     телефоне шире экрана. Замерено: содержимое 399 точек при экране 390 и
     при 360; у товара с фотографиями - ровно по экрану.
     Отчего. У галереи основа flex - 380 точек. В строку (широкий экран)
     основа задаёт ШИРИНУ, и всё в порядке. А здесь раскладка колоночная, и
     та же основа задаёт ВЫСОТУ: пустая галерея получает высоту 380, которую
     никто не просил. У неё же есть соотношение сторон 1:1 - и оно честно
     превращает эту высоту в ширину 380, при колонке в 352.
     flex-basis: auto убирает навязанную высоту: ширина берётся по колонке
     (352), высоту из неё считает соотношение сторон - квадрат по месту.
     Правило живёт ВНУТРИ этого запроса и только здесь: в строку основа 380
     нужна и работает правильно, трогать её там нечего. */
  .product-gallery { flex-basis: auto; }
}
/* Дополнение к Заданию Сайт №82 (Michael) - ПЛАВАЮЩИЕ КНОПКИ НЕ ПОВЕРХ
   ТОВАРОВ. На компьютере они стоят в углу пустого поля и никому не мешают;
   на телефоне под ними всегда карточка товара - экран узкий, свободного
   угла на нём нет.
   ЧТО ВЫБРАНО И ПОЧЕМУ. Уменьшить нельзя: 44x44 - это наименьший размер,
   по которому уверенно попадают пальцем, и он выбран не на глаз (Сайт
   №33). Двигать некуда: любой угол узкого экрана занят товаром.
   Поэтому кнопки убираются, пока человек листает ВНИЗ, то есть читает
   список, и возвращаются от любого движения ВВЕРХ - жеста, которым как раз
   и ищут, за что нажать. Это отвечает на жалобу буквально: в покое, когда
   человек остановился и смотрит на карточки, кнопок над ними нет.
   Почему не "прятать во время прокрутки и показывать в покое": это ровно
   наоборот - кнопки возвращались бы именно тогда, когда мешают.
   Что не потеряно: наверху страницы кнопки есть всегда; открытая панель
   (связи или доступности) кнопку не прячет; на компьютере не меняется
   ничего. Без JavaScript тоже ничего не меняется - класс не ставится, и
   кнопки просто всегда на месте, как раньше. */
@media (max-width: 700px) {
  .fabs-away .contact-fab, .fabs-away .a11y-fab {
    transform: translateY(90px);
    opacity: 0;
    pointer-events: none;
  }
  .contact-fab, .a11y-fab { transition: transform 0.18s ease, opacity 0.18s ease; }
  /* Сайт №37 - у кого выключено движение, у того ничего не едет: кнопки
     просто исчезают и появляются. */
  html.a11y-motion .contact-fab, html.a11y-motion .a11y-fab { transition: none; }
}

@media (max-width: 480px) {
  .product-grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 10px; }
  .site-header__inner { gap: 10px; }
  /* Задание Сайт №82 - остаток того же провала. 60px отступа снизу у
     содержимого плюс 60px до подвала - это мера для широкого экрана, где
     они читаются как поля страницы. На телефоне шириной в 375px они
     складываются в 120px пустоты подряд, и человек, долистав до конца
     списка, видит пустой экран вместо подвала. Половины хватает: подвал
     по-прежнему отделён от списка, но не отодвинут за край экрана. */
  /* Задание Сайт №84 - Michael: "расстояние до подвала всё ещё большое".
     После Сайта №82 от строки листания до подвала оставалось 48 точек (24
     отступа снизу у содержимого плюс 24 до подвала). Сокращено вдвое: 24
     точки на двоих. Подвал по-прежнему отделён - у него своя черта сверху
     и свои 24 точки внутреннего отступа, - но пустого места под последней
     строкой больше нет. */
  .site-main { padding-bottom: 12px; }
  .site-footer { margin-top: 12px; }
}

/* Задание Сайт №82 - пункт "письмо в Gmail" только там, где он работает.
   (hover: none) и (pointer: coarse) вместе - это признак пальца, а не
   мыши: телефон и планшет. Ноутбук с сенсорным экраном сюда не попадает,
   у него основное устройство ввода всё равно мышь.
   ВАЖНО: правило должно оставаться ПОСЛЕДНИМ в этом файле. Оно спорит по
   display с .share-links__btn и .pay-alt__link, а сила у селекторов
   одинаковая - решает порядок. Новые правила добавлять ВЫШЕ. */
/* Задание Сайт №84 (Michael) - ПОЧЕМУ ЭКРАН ЕЗДИТ ВБОК ПРИ ПОИСКЕ.
   Правка Сайта №83 не помогла, и не могла: список подсказок был ни при чём.
   Замер при открытом списке, окно 390: ширина содержимого страницы
   (documentElement.scrollWidth) - 390 точек, ровно ширина экрана; шире
   экрана только содержимое самой ленты плиток, но она свой собственный
   прокручиваемый ящик и наружу не выходит. То есть на странице НЕТ ничего
   шире экрана - ни списка, ни строки поиска, ни шапки.
   А вот что есть: у поля поиска размер текста 15,2 точки (0.95rem),
   замерено. Safari на iPhone САМ УВЕЛИЧИВАЕТ страницу, когда человек
   касается поля ввода с размером текста меньше 16 точек. После увеличения
   разметка шириной 390 точек перестаёт помещаться в видимую область - и
   страницу становится можно возить вправо-влево. Отсюда и "экран ездит", и
   то, что в обычном браузере в узком окне это не воспроизводится: там
   такого увеличения не бывает.
   Меньше 16 точек оказались не только поле поиска: цена от и до (13,3),
   выбор валюты (13,3), выбор порядка (14,4) - все они дают то же самое.
   Что сделано: на устройствах с пальцем вместо мыши у всех полей ввода и
   списков выбора размер текста ровно 1rem. Именно rem, а не 16px: корневой
   размер - 16 точек, а панель доступности (Сайт №37) его только
   УВЕЛИЧИВАЕТ (112,5%...160%), поэтому 1rem никогда не окажется меньше
   16 точек и при этом растёт вместе с текстом сайта - правило Сайта №40
   не нарушено.
   Чего делать НЕЛЬЗЯ: писать в мета-теге maximum-scale или
   user-scalable=no. Это убрало бы увеличение вместе с проблемой - и
   отняло бы у человека с плохим зрением возможность приблизить страницу
   пальцами, ровно то, что обещает заявление о доступности. */
@media (hover: none) and (pointer: coarse) {
  /* Задание Сайт №85 - см. объяснение у .breadcrumbs выше. 11 точек сверху
     и снизу к строке в 18 точек дают цель в 40 точек высотой. */
  .breadcrumbs a, .breadcrumbs > span { padding-block: 11px; }
}

@media (hover: none) and (pointer: coarse) {
  input:not([type="checkbox"]):not([type="radio"]),
  select,
  textarea,
  /* Выбор порядка задан классом, и правило по классу сильнее правила по
     имени элемента - без этой строки он один остался бы 14,4 точки.
     Проверено замером: после правки все поля и списки ровно 16 точек. */
  .browse__sort-select {
    font-size: 1rem;
  }
}

@media (hover: none) and (pointer: coarse) {
  .gmail-desktop-only { display: none; }
}

/* Сайт №97 - обратная сторона того же правила: пункты, которые имеют смысл
   только на устройстве с сенсорным экраном. Телефон (tel: там звонит, а на
   компьютере вызывает окно "Open People?") и письмо через почтовую
   программу (на телефоне она есть всегда, на компьютере может быть не
   назначена вовсе - тогда mailto молча не делает ничего). */
@media (hover: hover) and (pointer: fine) {
  .contact-touch-only { display: none; }
}
