/* =====================================================================
   TeachMe — витрина
   =====================================================================

   Визуальная идея — бумага. Учитель покупает не «файл», а лист,
   который через десять минут окажется на парте. Отсюда карточка-лист
   со стопкой под ней, наклонённые страницы в первом экране и
   «маркерное» выделение в заголовке — так, как выделяют главное
   в распечатке.

   Порядок разделов:
     1. Токены
     2. Сброс и база
     3. Типографика
     4. Кнопки, поля, бейджи
     5. Шапка и меню
     6. Первый экран
     7. Преимущества
     8. Каталог: фильтры, сетка, карточка
     9. Об авторе
    10. Отзывы
    11. FAQ
    12. Подвал
    13. Корзина и модальное окно
    14. Внутренние страницы
    15. Служебные состояния
    16. Адаптив
   ===================================================================== */

/* ---------------------------------------------------------------------
   1. Токены
   --------------------------------------------------------------------- */
:root {
    /* Светлая тема по умолчанию. Переключается ниже — системной
       настройкой или выбором пользователя. Все цвета описаны функцией
       light-dark(светлый, тёмный) и следуют за этим свойством. */
    color-scheme: light;

    /* Палитра снята с макета пипеткой, а не подобрана на глаз:
       розовая кнопка #FC3C82, светло-розовая плашка карточки #FEB0CC,
       подложки блоков #FFF2F6 и #EBF7FF, линии #E5E6E9, фон шапки
       #FDFDFD, чёрный текст, серые подписи.

       Правило распределения ролей: розовый — всё, что несёт смысл
       (ссылки, главная кнопка, выделение), голубой — служебные метки,
       чёрный — текст. Цветного на экране мало, и работает именно
       контраст с пустотой, а не количество краски.

       Одна поправка к макету, и она вынужденная. Белая надпись
       по #FC3C82 даёт 3.44 при норме 4.5 (WCAG 2.1, критерий 1.4.3),
       а этим цветом залиты все главные кнопки. Тон и насыщенность
       сохранены, опущена только светлота.

       Считано по самой светлой поверхности, где цвет встречается,
       а не по белому листу. Первый расчёт дал #EA0458 (4.51 на белом),
       но на плашке --paper тот же цвет давал 4.11: разница маленькая,
       а норму переводит из «проходит» в «нет». #DD0453 даёт 4.53
       на плашке и 4.98 на белом. */
    --indigo:      #DD0453;
    --indigo:      light-dark(#DD0453, #FF7FB5);
    --indigo-dark: #C00347;
    --indigo-dark: light-dark(#C00347, #FFA8CB);
    /* Подложка розового блока — прямо из макета */
    --indigo-soft: #FFF2F6;
    --indigo-soft: light-dark(#FFF2F6, #2A1220);
    /* Светло-розовая плашка «в корзину» на карточке. Ею ничего
       не пишут — на ней лежит тёмная иконка. */
    --coral:       #FEB0CC;
    --coral:       light-dark(#FEB0CC, #FF6FAC);
    --coral-dark:  #FD97BC;
    --coral-dark:  light-dark(#FD97BC, #FF93C2);
    --mint:        #00AAE8;
    --mint:        light-dark(#00AAE8, #6FD0F5);
    /* Подложка голубого блока и его обводка — из макета */
    --mint-soft:   #EBF7FF;
    --mint-soft:   light-dark(#EBF7FF, #12303C);
    --mint-line:   #CDE7F9;
    --mint-line:   light-dark(#CDE7F9, #1D4557);

    /* Те же цвета, но пригодные для ТЕКСТА.
       Живая фуксия #FF4E9B на белом даёт 3.08 при норме 4.5 (WCAG 2.1,
       критерий 1.4.3), голубой #00AAE8 — 2.65: ими нельзя писать,
       только заливать. Для текста берётся тот же тон, доведённый
       до нормы затемнением — оттенок остаётся фирменным (338°). */
    --indigo-ink:  #DD0453;
    --indigo-ink:  light-dark(#DD0453, #FF7FB5); /* 4.53 на самой светлой плашке */
    --mint-ink:    #00688F;
    --mint-ink:    light-dark(#00688F, #6FD0F5); /* по --mint-soft: 5.55 */
    /* Заливка светло-розовой плашки — прямо из макета. Надпись и
       значок по ней тёмные, и это поправка к макету: там значок
       корзины нарисован цветом #FEC8DC по #FEB0CC, то есть контраст
       1.1 — кнопка выглядит пустой полосой. Цвет плашки сохранён,
       значок сделан тёмным: 10.65. */
    --coral-ink:   #FEB0CC;
    --coral-ink:   light-dark(#FEB0CC, #FF6FAC);
    /* Цвет ТЕКСТА на цветной кнопке. Значения по умолчанию
       рассчитаны под палитру выше; при смене цветов из админки
       они пересчитываются на сервере по контрасту WCAG
       (App\Helpers\Design::contrastText) и приезжают сюда
       отдельным <style>. Поэтому подобранная доступность
       не ломается от одного неудачного оттенка в пипетке. */
    --on-primary:  #FFFFFF;
    --on-primary:  light-dark(#FFFFFF, #1A0A12);
    --on-accent:   #14141A;
    --on-accent:   light-dark(#14141A, #1A0A12);
    --coral-ink-d: #FD97BC;
    --coral-ink-d: light-dark(#FD97BC, #FF93C2);   /* наведение */

    /* Чернила чёрные и чуть холодные — как заголовок на обложке.
       Чистый #000 на экране режет глаз на длинном тексте, разница
       с #15151A на глаз незаметна. */
    --ink:         #15151A;
    --ink:         light-dark(#15151A, #F0F0F4);
    /* Серый подписей. В макете #A6A7AF — 2.40 на белом при норме 4.5;
       им набраны категория товара и подписи под значками, то есть
       текст, а не украшение.

       Норма проверена не на белом листе, а на самой светлой плашке,
       где этот серый встречается: на --paper и на розовой подложке
       первого экрана. Промежуточный #757682 проходил только на белом
       (4.50), а на плашке давал 4.09. */
    --ink-muted:   #6E6F7B;
    --ink-muted:   light-dark(#6E6F7B, #9C9CAC);
    /* Этим цветом написана старая (зачёркнутая) цена, а она сообщает
       размер скидки, то есть информативна и обязана читаться. */
    --ink-faint:   #6E6F7B;
    --ink-faint:   light-dark(#6E6F7B, #9C9CAC);

    /* Страница белая, плашки — холодный светло-серый: ровно тот фон,
       на котором сидит кот. Карточки и кнопки выступают серыми
       пятнами на чистом листе, цвет остаётся только акценту. */
    --paper:       #F4F4F6;
    --paper:       light-dark(#F4F4F6, #1A1A20);
    --canvas:      #FFFFFF;
    --canvas:      light-dark(#FFFFFF, #101014);
    --line:        #E5E6E9;
    --line:        light-dark(#E5E6E9, #2E2E38);

    /* Поверхности, которые не следуют за сменой темы автоматически */
    --ink-body:      #33333B;
    --ink-body:      light-dark(#33333B, #D2D2DC); /* длинный текст: чуть мягче заголовков */
    /* Шапка в макете сплошная, а не прозрачная: #FDFDFD, на полтона
       светлее страницы. Прозрачность с размытием убрана — под шапкой
       в макете нечему просвечивать. */
    --header-bg:     #FDFDFD;
    --header-bg:     light-dark(#FDFDFD, #16161C);
    --badge-plate:   rgba(255, 255, 255, .94);
    --badge-plate:   light-dark(rgba(255, 255, 255, .94), rgba(10, 10, 13, .88));
    /* Подвал в макете светлый: белый лист, серые надписи. Тёмная
       плита во всю ширину была последним, что видит человек, и
       обрывала страницу чёрной полосой.

       Серый взят не из макета как есть: там #BCBDC3, а это 1.87
       на белом при норме 4.5 — юридический текст таким набирать
       нельзя. Тон сохранён, светлота опущена до нормы. */
    --footer-bg:     #FFFFFF;
    --footer-bg:     light-dark(#FFFFFF, #0A0A0D);
    /* Тёмная плашка, которая обязана быть тёмной в обеих темах:
       баннер про cookie. Своё значение, а не ссылка на подвал —
       подвал светлый, и одолженный цвет однажды уже подвёл. */
    --plate-dark:    #15151A;
    --footer-ink:    #6E6F7B;
    --footer-ink:    light-dark(#6E6F7B, #D8D8E0);
    --footer-ink-dim:#6E6F7B;
    --footer-ink-dim:light-dark(#6E6F7B, #9C9CAC);

    /* #16A34A на белом — 3.15. Этим цветом набрана строка скидки
       в корзине, то есть сумма, ради которой вводят промокод. */
    --success:     #15803D;
    --success:     light-dark(#15803D, #4ADE80);
    /* Текст на зелёной заливке: в тёмной теме --success светлеет
       до #4ADE80, и белая надпись «В корзине» давала 1.74 — кнопка
       читалась как пустой зелёный прямоугольник. */
    --on-success:  #FFFFFF;
    --on-success:  light-dark(#FFFFFF, #12210F);
    --success-hover: #15803D;
    --success-hover: light-dark(#15803D, #86EFAC);
    /* Тёмнее прежнего #DC2626 на один шаг. Причина считана: на белом
       тот давал 4.53 и норму проходил, а на --paper (#F4F4F6) — 4.40
       при норме 4.5. Этим цветом набраны «Удалить» и «Заблокировать»
       в админке, то есть подписи к необратимым действиям, и лежат они
       как раз на --paper. #D42121 даёт 4.75 на бумаге и 5.20 на белом. */
    --danger:      #D42121;
    --danger:      light-dark(#D42121, #FCA5A5);
    /* Текст на красной заливке. В тёмной теме --danger светлеет,
       и белая надпись по нему давала 1.9 при норме 4.5: кнопка
       «Удалить» превращалась в розовый прямоугольник без слов. */
    --on-danger:   #FFFFFF;
    --on-danger:   light-dark(#FFFFFF, #450A0A);
    /* Наведение: в светлой теме темнее основного, в тёмной — светлее.
       Один #B91C1C на обе темы означал бы, что в тёмной кнопка при
       наведении не подсвечивается, а гаснет. */
    --danger-hover: #B91C1C;
    --danger-hover: light-dark(#B91C1C, #FECDD3);

    /* Тени холодные и почти незаметные: на обложке теней нет вовсе,
       всё лежит плоско на белом */
    --shadow-sm:   0 1px 2px rgba(21, 21, 26, .06);
    --shadow-sm:   light-dark(0 1px 2px rgba(21, 21, 26, .06), 0 1px 2px rgba(0, 0, 0, .4));
    --shadow-md:   0 4px 16px rgba(21, 21, 26, .08);
    --shadow-md:   light-dark(0 4px 16px rgba(21, 21, 26, .08), 0 4px 16px rgba(0, 0, 0, .45));
    --shadow-lg:   0 12px 32px rgba(21, 21, 26, .12);
    --shadow-lg:   light-dark(0 12px 32px rgba(21, 21, 26, .12), 0 12px 32px rgba(0, 0, 0, .5));

    /* Скругления из макета. Их там ровно два: 10 у карточек, кнопок
       и блоков, 5 — у мелких плашек вроде полосы «в корзину».
       Острых прямоугольников нет ни одного.

       Числа обязаны совпадать с Design::RADII['normal']: сервер не
       дописывает переменную, когда выбрано умолчание. */
    --radius-sm:   5px;
    --radius:      10px;
    --radius-lg:   20px;

    --container:   1180px;   /* колонка содержимого из макета */
    --gutter:      24px;
    /* Высота шапки. Одно число на четверых: сама шапка, выпадающее
       меню под ней, всплывающие сообщения и — главное — отступ
       прокрутки к якорям. Порознь они уже разъезжались: меню на
       телефоне знало про 64, а сообщения так и висели на 88. */
    --header-h:    88px;

    --ease:        cubic-bezier(.4, 0, .2, 1);
    --speed:       .22s;

    /* Одна гарнитура на всё — так набран макет: логотип, заголовки,
       текст и подписи в подвале одним Raleway. Разные начертания
       разводят их сильнее, чем это сделали бы два семейства. */
    --font: 'Raleway', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    /* Заголовочный токен остался, но указывает на ту же гарнитуру.
       Не выброшен потому, что им размечены логотип и заголовки: если
       когда-нибудь снова захочется развести текст и заголовки на два
       семейства, менять придётся одну строку, а не тридцать мест. */
    --font-display: var(--font);
}

/* ---------------------------------------------------------------------
   2. Сброс и база
   --------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

/* Атрибут hidden обязан прятать — всегда и без исключений.
 *
 * У браузера для этого есть своё правило [hidden]{display:none}, но
 * оно из таблицы браузера и проигрывает любому display в таблице
 * автора. Достаточно написать где-нибудь `.zoom { display: flex }` —
 * и element.hidden = true перестаёт работать молча: атрибут в DOM
 * стоит, элемент на экране.
 *
 * Так и было: окно с увеличенной картинкой товара не закрывалось ни
 * крестиком, ни щелчком мимо, ни Escape. Скрипт отрабатывал верно,
 * прятать мешал CSS. Тем же были сломаны строка скидки в корзине,
 * кнопка «Показать ещё» и панель массовых действий в админке.
 *
 * !important здесь оправдан: элемента, которому нужно показываться
 * с hidden, не существует. */
[hidden] { display: none !important; }

html {
    -webkit-text-size-adjust: 100%;
    scroll-behavior: smooth;
    /* Прокрутка к якорю (#about, #catalog, #faq) останавливается
       не вровень с верхом окна, а ниже липкой шапки. Без этого
       клик по «Обо мне» прятал под шапкой 33 точки карточки —
       ровно её заголовок.

       scroll-padding у прокручиваемого элемента, а не scroll-margin
       у каждой секции: якорь может появиться на любой странице,
       и перечислять их по одному — значит однажды забыть. */
    scroll-padding-top: calc(var(--header-h) + 20px);
}

body {
    margin: 0;
    font-family: var(--font);
    font-weight: 400;
    font-size: 16px;
    line-height: 1.65;
    color: var(--ink);
    background: var(--canvas);
    -webkit-font-smoothing: antialiased;

    /* Цифры одинаковой ширины: цены в сетке карточек не пляшут */
    font-variant-numeric: tabular-nums;

    /* Ни одно слово не должно выталкивать страницу вбок. Правило
       срабатывает только когда слово иначе не помещается, поэтому
       на обычный текст не влияет. */
    overflow-wrap: break-word;
}

img, svg, video { max-width: 100%; height: auto; display: block; }

button, input, select, textarea { font: inherit; color: inherit; }

a { color: var(--indigo); text-decoration: none; transition: color var(--speed) var(--ease); }
a:hover { color: var(--indigo-dark); }

ul, ol { margin: 0; padding: 0; list-style: none; }

hr { border: 0; border-top: 1px solid var(--line); margin: 24px 0; }

/* Видимый фокус — обязателен: по сайту ходят и с клавиатуры */
:focus-visible {
    outline: 3px solid var(--indigo);
    outline-offset: 2px;
    border-radius: 4px;
}

.skip-link {
    position: absolute;
    left: -9999px;
    top: 8px;
    z-index: 200;
    padding: 10px 18px;
    background: var(--paper);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-md);
}
.skip-link:focus { left: 16px; }

.container {
    width: 100%;
    max-width: var(--container);
    margin: 0 auto;
    padding: 0 var(--gutter);
}
.container--narrow { max-width: 780px; }

.section { padding: 88px 0; }
.section--tight { padding: 56px 0; }

/* ---------------------------------------------------------------------
   3. Типографика
   --------------------------------------------------------------------- */
/* Заголовки — заголовочной гарнитурой и жирно, как на обложке:
   там «teachme.yulia» набрано тяжёлым геометрическим гротеском,
   и весь её характер держится на этом контрасте с обычным текстом. */
h1, h2, h3, h4 {
    margin: 0;
    font-family: var(--font-display);
    font-weight: 800;
    line-height: 1.15;
    letter-spacing: -.02em;
}

/* Надзаголовок набран строчными, а не капителью.
   Разрядка капителью кричит «рекламный блок»; строчная с широкой
   разрядкой звучит тише и не спорит с самим заголовком. */
.eyebrow {
    margin: 0 0 14px;
    font-size: 13px;
    font-weight: 500;
    letter-spacing: .1em;
    text-transform: lowercase;
    color: var(--ink-muted);
}

/* Перенос длинных слов.

   overflow-wrap здесь как страховка: он срабатывает только тогда,
   когда слово иначе вылезет за край, и на обычный текст не влияет
   вовсе. Без него «конфиденциальности» в заголовке при ширине окна
   375 просило 352 пикселя при колонке в 328, и вбок уезжала вся
   страница.

   hyphens: auto добавлен ради вида: без него слово рвётся посреди
   слога и без чёрточки. У документа стоит lang="ru", поэтому браузер
   переносит по правилам русского языка. */
.section__title {
    font-size: clamp(30px, 3.8vw, 46px);
    letter-spacing: -.03em;
    max-width: 20ch;
    overflow-wrap: break-word;
    hyphens: auto;
}
.section__title--sm { font-size: clamp(22px, 2.4vw, 28px); margin-bottom: 24px; }

/* Акцентное слово заголовка — на розовой плашке, ровно как подпись
   «идеи и материалы для уроков» на обложке и на аватарке сообщества.
   Это главный опознавательный приём бренда, и он же снимает вопрос
   контраста: белым по #CF2464 — 4.78 при любом кегле.

   box-decoration-break нужен, чтобы при переносе плашка получила
   скругление на каждом куске, а не одно на всю строку. */
/* Выделенное слово заголовка — просто жирное, без плашки.

   Плашка осталась от прежнего оформления, где заголовок набирался
   одним весом и выделять было нечем. В макете «Материалы» выделено
   насыщенностью: 800 против 500 у остального. Розовый прямоугольник
   поверх этого спорил бы с кнопкой ниже — на первом экране
   и так одно цветное пятно. */
.accent-word { font-weight: 800; }

.section__head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 24px;
    margin-bottom: 36px;
    flex-wrap: wrap;
}
.section__count { margin: 0; color: var(--ink-muted); font-weight: 500; }


/* Скрыто визуально, но доступно программам чтения с экрана.
   clip-path вместо display:none — скрытое через display содержимое
   скринридеры не читают вовсе, а это заголовок раздела. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

.muted { color: var(--ink-muted); }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .92em; }
.nowrap { white-space: nowrap; }

/* Длинный текст из редактора */
.prose { max-width: 70ch; font-size: 17px; color: var(--ink-body); }
.prose h2 { font-size: 26px; margin: 40px 0 14px; }
.prose h3 { font-size: 21px; margin: 32px 0 12px; }
.prose p { margin: 0 0 16px; }
.prose ul, .prose ol { margin: 0 0 16px; padding-left: 22px; }
.prose ul { list-style: disc; }
.prose ol { list-style: decimal; }
.prose li { margin-bottom: 8px; }
.prose a { text-decoration: underline; }
.prose img { border-radius: var(--radius); margin: 20px 0; }
.prose table { width: 100%; border-collapse: collapse; margin: 20px 0; }
.prose th, .prose td { border: 1px solid var(--line); padding: 10px 12px; text-align: left; }
.prose blockquote {
    margin: 20px 0;
    padding: 4px 0 4px 20px;
    border-left: 3px solid var(--indigo);
    color: var(--ink-muted);
    font-style: italic;
}

/* ---------------------------------------------------------------------
   4. Кнопки, поля, бейджи
   --------------------------------------------------------------------- */
/* Кнопки без теней, со скруглением и обычным набором.
   Прописные с разрядкой пришли от прежнего образца; на обложке
   заглавными набран один голубой ярлык, а всё остальное — обычным
   регистром, и кричащая кнопка в этот характер не встаёт.
   Тень на кнопке изображает физическую клавишу — приём из времён,
   когда нажимаемое нужно было объяснять; сейчас он только утяжеляет. */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 13px 26px;
    border: 1px solid transparent;
    /* Скругление кнопок в макете — 10, как у карточек, а не 5 */
    border-radius: var(--radius);
    font-weight: 600;
    font-size: 15px;
    line-height: 1.2;
    text-align: center;
    cursor: pointer;
    transition: transform var(--speed) var(--ease),
                background-color var(--speed) var(--ease),
                border-color var(--speed) var(--ease),
                color var(--speed) var(--ease);
    background: none;
}
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: .5; cursor: not-allowed; }

.btn--primary { background: var(--indigo-ink); color: var(--on-primary); }
.btn--primary:hover { background: var(--indigo-dark); color: var(--on-primary); }

.btn--accent { background: var(--coral-ink); color: var(--on-accent); }
.btn--accent:hover { background: var(--coral-ink-d); color: var(--on-accent); }

/* Спокойная кнопка — тёплая плашка без рамки.
   Рамка вокруг светло-бежевого прямоугольника на белом листе рисует
   вторую линию там, где хватает одной: сама плашка уже видна. */
.btn--ghost { background: var(--paper); color: var(--ink); border-color: transparent; }
.btn--ghost:hover { background: var(--indigo-soft); color: var(--ink); }

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

   Узнаваемость держит значок — он и остался синим #0069E0. */
/* Кнопка входа в макете розовая, как все главные кнопки. Собственной
   заливки у неё больше нет — она берёт её у .btn--primary, рядом с
   которым и стоит в разметке.

   Раньше здесь было «background: var(--paper)», и это перебивало
   .btn--primary: специфичность у обоих одинаковая, а правило ниже
   по файлу побеждает. Кнопка оставалась серой, сколько классов
   на неё ни вешай.

   Значок ВКонтакте наследует цвет надписи: фирменный синий на розовой
   заливке даёт 1.7 и выглядит грязным пятном. */
.btn--vk svg { color: currentColor; }

.btn--link, .btn--danger-text { font-weight: 500; }
.btn--link { padding: 6px 4px; color: var(--indigo-ink); }
.btn--link:hover { color: var(--indigo-dark); text-decoration: underline; }

.btn--danger { background: var(--danger); color: var(--on-danger); }
.btn--danger:hover { background: var(--danger-hover); color: var(--on-danger); }
.btn--danger-text { color: var(--danger); }
.btn--danger-text:hover { color: var(--danger-hover); }

/* Скругление у мелких кнопок своё: в макете у кнопок в 30 точек
   высотой оно 5, а у больших в 50 — 10. Одно и то же число на обе
   высоты смотрится по-разному. */
.btn--sm { padding: 9px 16px; font-size: 14px; border-radius: var(--radius-sm); }
.btn--lg { padding: 16px 34px; font-size: 16px; }
.btn--block { width: 100%; }

/* Кнопка «в корзину» после добавления */
.btn.is-added { background: var(--success); color: var(--on-success); }
.btn.is-added:hover { background: var(--success-hover); }

.input {
    width: 100%;
    padding: 11px 14px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--paper);
    color: var(--ink);
    transition: border-color var(--speed) var(--ease), box-shadow var(--speed) var(--ease);
}
.input:hover { border-color: var(--ink-muted); }
.input:focus {
    outline: none;
    border-color: var(--indigo);
    box-shadow: 0 0 0 3px rgba(74, 108, 247, .15);
}
.input--num { width: 110px; }
.input--sm { padding: 7px 10px; font-size: 14px; }
textarea.input { resize: vertical; min-height: 90px; }

.field { display: block; margin-bottom: 18px; }
.field__label {
    display: block;
    margin-bottom: 6px;
    font-size: 14px;
    font-weight: 600;
    color: var(--ink);
}
/* Не --coral: он для заливок, а звёздочка — мелкий знак на белом,
   где даёт 2.78 при норме 4.5. Именно она сообщает, что поле
   обязательное, и обязана читаться. */
/* Звёздочка обязательного поля. Была --coral-ink: #FF4E9B на белом
   даёт 3.08 при норме 4.5, а это не украшение, а единственный знак,
   которым поле объявлено обязательным. --indigo-ink того же розового
   рода, но темнее: 5.16. */
.field__label b { color: var(--indigo-ink); }
.field__hint { display: block; margin-top: 6px; font-size: 13px; color: var(--ink-muted); line-height: 1.5; }
.field--row { display: flex; gap: 16px; }
.field--row .field { flex: 1; margin-bottom: 0; }

.check { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; cursor: pointer; font-size: 15px; }
.check input { width: 18px; height: 18px; accent-color: var(--indigo); cursor: pointer; }

.badge {
    display: inline-flex;
    align-items: center;
    padding: 5px 11px;
    border-radius: var(--radius-sm);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    line-height: 1.4;
}
/* Сначала однотонный запасной фон, потом color-mix: в браузерах
   без его поддержки бейдж останется читаемым, а не превратится
   в цветной текст на прозрачном фоне */
.badge--category {
    background: var(--indigo-soft);
    background: color-mix(in srgb, var(--badge, var(--indigo)) 14%, white);

    /* Текст затемняем на четверть от выбранного цвета категории.
       Цвет задаёт администратор, и взятый как есть он часто не
       дотягивает до контраста 4.5 на своей же светлой подложке —
       фирменный индиго даёт всего 4.39. Подмешивание чёрного
       работает для любого оттенка, который выберут в админке,
       и не требует держать вторую палитру. */
    color: var(--indigo-ink);
    color: color-mix(in srgb, var(--badge, var(--indigo)) 62%, black);
}
.badge--discount { background: var(--coral-ink); color: var(--on-accent); }
.badge--sm { padding: 3px 9px; font-size: 11px; }

.chip {
    display: inline-flex;
    padding: 4px 10px;
    border-radius: var(--radius-sm);
    background: var(--paper);
    border: 1px solid var(--line);
    font-size: 12px;
    font-weight: 500;
    color: var(--ink-muted);
}
/* Рамка сливается с заливкой: прежний #A5F3FC остался от холодной
   палитры и на тёплом фоне светился голубым ободком, а обводить
   плашку её же цветом незачем — она и так видна. */
.chip--level { color: var(--mint-ink); border-color: var(--mint-soft); background: var(--mint-soft); }

.price { margin: 0; display: flex; align-items: baseline; gap: 10px; }
.price__now { font-size: 19px; font-weight: 600; letter-spacing: -.01em; }
.price__old { color: var(--ink-faint); font-size: 15px; font-weight: 400; }
.price--lg .price__now { font-size: 32px; }

/* ---------------------------------------------------------------------
   5. Шапка и меню
   --------------------------------------------------------------------- */
.header {
    position: sticky;
    top: 0;
    z-index: 60;
    background: var(--header-bg);
    border-bottom: 1px solid var(--line);
}
.header__inner {
    display: flex;
    align-items: center;
    gap: 24px;
    height: var(--header-h);
}

/* Знак в две строки: название и подпись под ним.
   Колонка, а не строка: в макете подпись стоит именно под словом
   и выровнена с ним по левому краю. */
.logo {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 2px;
    color: var(--ink);
    /* Не сжимается. Название магазина — единственное слово в шапке,
       которое обязано читаться целиком; уступать место будет меню,
       у которого для этого есть бургер. */
    flex-shrink: 0;
}
.logo:hover { color: var(--ink); }

/* Звёздочка висит у последней буквы и вверх, поэтому слово и она —
   отдельная строка с относительным позиционированием */
/* Звёздочка висит над последней буквой и чуть правее — как в макете.
   Слову оставлен запас справа, иначе она вылезала бы за знак
   и обрезалась соседним элементом шапки. */
.logo__word { position: relative; display: inline-flex; align-items: flex-start; padding-right: 15px; }
.logo__spark { position: absolute; right: -1px; top: -3px; width: 16px; height: 16px; flex-shrink: 0; }

/* Разрядка и верхний регистр — из макета. Подпись мелкая и служебная,
   поэтому набрана серым: чёрным она спорила бы с названием. */
.logo__tagline {
    font-size: 10px;
    font-weight: 500;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--ink-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Название магазина задаёт владелец, и оно бывает длинным.
   Без ограничения «TeachMe — методические разработки» на телефоне
   переносилось на три строки и разрывало шапку: блок вырастал
   до 89px при высоте шапки 65px и налезал на кнопки.
   Одна строка с многоточием — шапка держит любую длину. */
.logo__text {
    font-family: var(--font-display);
    font-weight: 800;
    /* Плавно, а не ступеньками. Шапка тесна на самых узких экранах,
       и подгонять её порогами бесполезно: после каждого находится
       следующий экран, где не хватает пары пикселей. Слово само
       уменьшается ровно настолько, насколько надо, и остаётся целым
       на любой ширине. 16px — предел читаемости. */
    font-size: clamp(16px, 4.6vw, 23px);
    letter-spacing: -.02em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}
/* В подвале места достаточно, и обрезать название незачем.
   Важнее другое: nowrap из .logo__text растягивал колонку сетки
   до ширины всей строки — 372px в контейнере 343px, — и на телефоне
   появлялась горизонтальная прокрутка всей страницы. */
/* Подвал стал светлым, и белый знак на нём пропал совсем. Цвет
   теперь берётся из палитры и следует за темой. */
.logo__text--footer {
    font-size: 20px;
    color: var(--ink);
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
}

/* Меню строчными буквами и без плашек: шапка обязана быть тише
   всего остального на экране. Подчёркивание при наведении вместо
   заливки — оно занимает ровно ту площадь, что и слово, поэтому
   пункты не «прыгают» в размере. */
.nav { display: flex; gap: 6px; margin-left: auto; }
/* Пункты меню в макете набраны обычным регистром и почти чёрным.
   Прежний lowercase приводил «FAQ» к «faq», то есть ломал слово,
   которое пишется прописными по определению. */
.nav__link {
    padding: 8px 10px;
    color: var(--ink-body);
    font-weight: 500;
    font-size: 15px;
    border-bottom: 1px solid transparent;
    transition: color var(--speed) var(--ease), border-color var(--speed) var(--ease);
}
.nav__link:hover { color: var(--indigo); border-bottom-color: transparent; }

.header__actions { display: flex; align-items: center; gap: 10px; }

/* Круглые кнопки соцсетей — из макета: серая обводка, значок внутри,
   при наведении подкрашиваются фирменным. */
.header__social {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    flex-shrink: 0;
    border: 1px solid var(--line);
    border-radius: 50%;
    background: var(--canvas);
    color: var(--ink-muted);
    transition: color var(--speed) var(--ease), border-color var(--speed) var(--ease);
}
.header__social:hover { color: var(--indigo); border-color: var(--indigo); }

.cart-button {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--paper);
    color: var(--ink);
    cursor: pointer;
    transition: border-color var(--speed) var(--ease), color var(--speed) var(--ease);
}
.cart-button:hover { border-color: var(--indigo); color: var(--indigo); }
.cart-button__count {
    position: absolute;
    top: -6px;
    right: -6px;
    min-width: 20px;
    height: 20px;
    padding: 0 5px;
    border-radius: 100px;
    /* Не --coral: белым по нему 2.78. --coral-ink — тот же оттенок,
       доведённый до нормы, а цвет надписи берётся парой к теме. */
    background: var(--coral-ink);
    color: var(--on-accent);
    font-size: 11px;
    font-weight: 700;
    line-height: 20px;
    text-align: center;
}
.cart-button__count.is-empty { display: none; }

.account { position: relative; }
.account__toggle {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 5px 12px 5px 5px;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--paper);
    cursor: pointer;
}
.account__toggle:hover { border-color: var(--indigo); }
.account__avatar { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; }
/* Буква вместо аватарки: --indigo по своей же светлой подложке
   даёт 4.40 при норме 4.5, поэтому берётся оттенок наведения */
.account__avatar--letter {
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--indigo-soft);
    color: var(--indigo-dark);
    font-weight: 700;
}
.account__name { font-size: 14px; font-weight: 600; }
.account__menu {
    position: absolute;
    right: 0;
    top: calc(100% + 8px);
    min-width: 190px;
    padding: 6px;
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
}
.account__item {
    display: block;
    width: 100%;
    padding: 9px 12px;
    border: 0;
    border-radius: var(--radius-sm);
    background: none;
    color: var(--ink);
    font-size: 14px;
    text-align: left;
    cursor: pointer;
}
.account__item:hover { background: var(--canvas); color: var(--indigo); }
.account__item-form { margin: 0; }

.burger {
    display: none;
    flex-direction: column;
    justify-content: center;
    gap: 5px;
    width: 44px;
    height: 44px;
    padding: 0 11px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--paper);
    cursor: pointer;
}
.burger span {
    display: block;
    height: 2px;
    background: var(--ink);
    border-radius: 2px;
    transition: transform var(--speed) var(--ease), opacity var(--speed) var(--ease);
}
.burger.is-open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.burger.is-open span:nth-child(2) { opacity: 0; }
.burger.is-open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

.promo-banner {
    background: linear-gradient(90deg, var(--indigo) 0%, var(--mint) 100%);
    color: #fff;
    text-align: center;
    padding: 11px 0;
    font-weight: 600;
    font-size: 15px;
}
.promo-banner a { color: #fff; text-decoration: underline; }

/* ---------------------------------------------------------------------
   6. Первый экран
   --------------------------------------------------------------------- */
/* Первый экран — одна светлая коробка со скруглением, а не полоса
   во всю ширину. Внутри слева обещание, справа картинка: так в макете.

   Страница под коробкой белая, поэтому коробка светло-розовая —
   иначе на белом её не видно вовсе. */
.hero { padding: 24px 0 28px; }


.hero__box {
    position: relative;
    display: grid;
    gap: 40px;
    align-items: center;
    padding: 48px;
    border-radius: var(--radius);
    background: var(--indigo-soft);
    /* Одна колонка: текст лежит поверх фотографии, а не рядом с ней */
    grid-template-columns: 1fr;
}

/* Коробка с коллажем.

   Ни подложки, ни полей: картинка и есть первый экран.

   Пропорции держатся жёстко, потому что место под текст оставлено
   В САМОЙ картинке: её левая половина пустая. Уплыви пропорции —
   и буквы поедут на карточки.

   Плёнки поверх фотографии больше нет. Она понадобилась, когда
   коллаж обрезался по ширине коробки и пёстрый край наезжал под
   текст. Теперь коллаж показывается целиком, в тех же пропорциях,
   в каких нарисован, и закрывать нечего. */
.hero__box--photo {
    /* Докуда идёт текст: в макете пустая часть коллажа — 47% ширины,
       дальше начинаются карточки. */
    --text-zone: 47%;
    /* Коллаж шире колонки текста: в макете 1180 против 1078, и он
       выходит за неё с обеих сторон. Выносим полями саму коробку,
       а не обнуляем поля контейнера: без фотографии на её месте
       розовая плашка, и ей в край ехать незачем. */
    margin-inline: calc(-1 * var(--gutter));
    /* Поля сверху и снизу — не отступ, а страховка: у самой узкой
       границы наложения (1025) текст ровно заполняет коробку
       по высоте и упирается в края картинки. С полем он либо дышит,
       либо, если совсем не влезает, растягивает коробку — и тогда
       чуть подрежется картинка, а не текст. */
    padding: 20px 0;
    background: none;
    border-radius: 0;
    aspect-ratio: 1180 / 592;
}

/* Фотография фоном. Тег остался <img> — на нём держится предпросмотр
   в админке, — поэтому фоном она становится позиционированием. */
.hero__image {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    /* Пропорции коробки и картинки совпадают, обрезать нечего.
       cover здесь страхует от чужой картинки других пропорций,
       а не кадрирует эту. */
    object-fit: cover;
}

/* Текст — над фотографией */
.hero__content { position: relative; z-index: 1; }

.hero__box--photo .hero__content {
    /* Цвет задан здесь, а не унаследован от страницы: заголовок
       своего color не имеет и берёт его от body, где --ink — уже
       тематический. Поверх светлой фотографии это означало белые
       буквы на белом; поймано глазами в тёмной теме. */
    color: var(--ink);
    max-width: var(--text-zone);
    /* Отступ в процентах, а не в пикселях: коллаж тянется вместе
       с колонкой, и текст обязан ехать вместе с ним. 5% — те же
       60 точек от края картинки, что в макете. */
    padding-left: 5%;
}

/* Поверх фотографии подзаголовок перестаёт быть серым.

   Под ним светлая часть коллажа (#F1F1F3), и мягкий --ink-body
   в тёмной теме на приглушённой картинке нормы уже не даёт.
   Разница между главным и второстепенным держится кеглем,
   а не светлотой. */
.hero__box--photo .hero__subtitle { color: var(--ink); }

.hero__title {
    font-size: clamp(30px, 3.6vw, 42px);
    line-height: 1.2;
    letter-spacing: -.025em;
    font-weight: 600;
    margin-bottom: 10px;
    max-width: 18ch;
}

.hero__subtitle {
    margin: 0 0 24px;
    font-size: clamp(16px, 1.75vw, 20px);
    font-weight: 400;
    color: var(--ink-body);
    max-width: 40ch;
}

/* Над фотографией строку ломает не счёт знаков, а пустая часть
   коллажа: 18ch и 40ch рвали бы заголовок раньше, чем в макете. */
.hero__box--photo .hero__title,
.hero__box--photo .hero__subtitle { max-width: none; }

/* Выделенное слово: в заголовке — весом, в подзаголовке — цветом.
   Ставится звёздочками в самом тексте, см. mark() в functions.php. */
.hero__subtitle .accent-word { color: var(--indigo-ink); font-weight: 600; }

/* --- Узкий экран: одна плашка, коллаж полосой снизу -------------------

   Ниже 1180 текст уходит с картинки, но остаётся с ней в ОДНОЙ плашке:
   фон коробки — тот же светло-серый, что фон коллажа, зазора между
   ними нет, скругление общее. Картинка не отдельный блок под текстом,
   а нижняя часть той же карточки.

   Почему не оставить текст поверх картинки, как на широком экране.
   Пустое место под текст нарисовано В КАРТИНКЕ, и занимает оно её
   левую половину — но «пустая» она только на своей ширине. Сожми
   картинку до телефона, и в кадр под буквами попадает то ручка,
   то тетрадь: замер на планшете дал подпись на синей ручке (#5072BF)
   с контрастом 1.13 при норме 4.5, и плёнка в 50% поднимала это лишь
   до 2.53. Совсем пустая полоса внутри коллажа — 15% его ширины,
   и текст в неё на узком экране не помещается.

   Раньше здесь был ещё худший вариант: картинка обрезалась по правому,
   самому пёстрому краю, и читаемость спасали плёнкой в 92% — от коллажа
   оставалась муть. Мешает не яркость, а пестрота, и правильный ответ —
   не гасить пёстрое, а не класть на него текст. */
@media (max-width: 1180px) {
    .hero__box--photo {
        /* Фон коробки = фон коллажа. Цвет постоянный, как и остальные
           цвета этой коробки: она светлая в любой теме. */
        --collage-bg: #F2F2F4;

        aspect-ratio: auto;
        /* Коробка возвращается в общие поля страницы */
        margin-inline: 0;
        border-radius: var(--radius);
        /* Картинка доходит до краёв плашки и обрезается по скруглению */
        overflow: hidden;
        background: var(--collage-bg);
        /* Снизу поля нет: там начинается картинка */
        padding: 28px 24px 0;
        gap: 22px;
    }

    .hero__box--photo .hero__content {
        /* Строка во всю плашку читалась бы как абзац, а не как
           обещание: 1069 точек при кегле 42. */
        max-width: 620px;
        padding-left: 0;
    }

    .hero__box--photo .hero__image {
        /* Картинка возвращается в поток и встаёт под текст —
           внутри той же плашки, без зазора и без своих углов. */
        position: static;
        order: 2;
        height: auto;
        /* Распираем боковые поля коробки, чтобы дойти до её краёв.
           max-width снимается: в сбросе у картинок стоит 100%,
           и он обрезал бы эти 48 обратно. */
        width: calc(100% + 48px);
        max-width: none;
        margin-inline: -24px;
        /* Полосой, а не целиком: во всю высоту коллаж занял бы
           пол-экрана. Кадр по правому краю — там карточки, ради
           которых картинка и стоит. */
        aspect-ratio: 2.6 / 1;
        object-position: right center;
    }
}

/* Планшет в альбомной ориентации: места хватает на две колонки,
   и плашка складывается как на широком экране — текст слева, коллаж
   справа. Картинка при этом не полоса снизу, а правая половина
   плашки во всю её высоту: так короче по вертикали и ближе к макету. */
@media (min-width: 769px) and (max-width: 1180px) {
    .hero__box--photo {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        align-items: center;
        /* Справа поля нет: там картинка до самого края */
        padding: 28px 0 28px 28px;
        gap: 28px;
        min-height: 360px;
    }

    .hero__box--photo .hero__content { max-width: none; }

    .hero__box--photo .hero__image {
        width: 100%;
        /* Поля коробки картинке не нужны: она доходит до верхнего
           и нижнего краёв плашки так же, как до правого. Тянется
           сама — height: auto плюс stretch, — а отрицательные поля
           выносят её за padding коробки. */
        margin: -28px 0;
        height: auto;
        align-self: stretch;
        /* Высоту задаёт текст соседней колонки, а не пропорции */
        aspect-ratio: auto;
    }
}

@media (max-width: 640px) {
    .hero__box--photo { padding: 24px 20px 0; }

    .hero__box--photo .hero__image {
        width: calc(100% + 40px);
        margin-inline: -20px;
        /* На телефоне полоса выше: в узкой карточки не разглядеть */
        aspect-ratio: 3 / 2;
    }
}

/* Поверх коллажа цвета не следуют за темой.

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

   Раньше было наоборот: картинка гасилась до 42% яркости, чтобы
   по ней можно было писать светлым. Коллаж от этого превращался
   в грязное пятно, а розовое слово подзаголовка не проходило норму
   НИ ПРИ КАКОЙ плотности — поверх приглушённой картинки читается
   только почти белое.

   Числа посчитаны по самым невыгодным точкам под каждой строкой
   (замер в браузере: коллаж рисуется в холст, под каждой строкой
   перебираются все точки):
     заголовок #15151A на #C0C0C2 — 10.02
     подзаголовок #15151A — 15.99
     розовое слово #D40450 на #F0F0F2 — 4.69
     подписи #6A6B77 — 4.63

   Оба цвета здесь на пару процентов темнее общих. Причина одна:
   общие считаны по плашке --paper (#F4F4F6), а точки под этими
   строками темнее её — #F0F0F2. Там --indigo-ink даёт 4.38,
   а --ink-muted 4.41 при норме 4.5. На глаз разницы нет.

   Действует на любой ширине: текст лежит поверх коллажа и
   на телефоне. В тёмной теме — наоборот, см. следующий блок. */
.hero__box--photo {
    --ink:        #15151A;
    --ink-body:   #33333B;
    --ink-muted:  #6A6B77;
    --indigo-ink: #D40450;
}

/* --- Тёмная тема: коллаж приглушается, текст светлеет ---------------

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

   Множитель 0.42 посчитан, а не подобран: он опускает самое светлое
   место коллажа (#F5F5F7) до уровня, на котором светлый текст даёт
   норму 4.5. Ниже уходить незачем — от картинки останется грязь.

   Серого здесь нет ни у чего: поверх приглушённой картинки серый
   не проходит норму НИ ПРИ КАКОЙ плотности, это уже было измерено.
   Разница между главным и второстепенным держится кеглем.

   Розовое слово подзаголовка в тёмной теме бледнеет до #FFE4EF:
   обычный розовый поверх приглушённого коллажа даёт 2.6 при норме
   4.5, и никакой оттенок фирменного розового там не проходит.

   Плашка узкого экрана (--collage-bg) — это ПРИГЛУШЁННЫЙ КОЛЛАЖ,
   а не просто тёмный цвет. Ниже 1180 текст лежит не на картинке,
   а на плашке, и картинка идёт полосой в той же карточке: если
   плашка и полоса разной светлоты, карточка распадается на две —
   тёмный прямоугольник с буквами и приклеенная под ним фотография.
   Ровно это и вышло, когда плашке дали #1A1A20: шов между ними
   был виден как 2.94 — хорошо различимая ступенька. У светлой темы
   его нет (1.02), потому что там плашка совпала с бумагой коллажа
   случайно.

   Поэтому цвет считается из яркости: бумага коллажа #F5F5F7,
   множитель 0.42, отсюда #676768. Меняешь множитель — меняй и цвет,
   иначе шов вернётся; в тестах эта связь проверяется счётом,
   а не глазами.

   Плашка вышла светлее, чем принято для тёмной темы, и это не
   недосмотр: она обязана быть цвета картинки. Светлый текст по ней
   даёт 4.97, приглушённый — 4.64, розовое слово — 4.73.

   Подложка значков доводов при этом наполовину прозрачна (см. файлы
   в assets/images/perks): сплошная чёрная монета поверх коллажа
   смотрелась тяжело.

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

   hue-rotate(180deg) возвращает розовому его тон — без поворота
   обратно он позеленел бы.

   Оба пути включения тёмной темы нужны: системная настройка
   и выбор кнопкой. Мутация показывала, почему — с одним лишь первым
   правилом человек, выбравший тему руками, остаётся с прежним
   оформлением. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .hero__box--photo {
        --ink:        #F0F0F4;
        --ink-body:   #F0F0F4;
        --ink-muted:  #E8E8F0;
        --indigo-ink: #FFE4EF;
        --collage-bg: #676768;
    }

    :root:not([data-theme="light"]) .hero__image { filter: brightness(.42); }
    :root:not([data-theme="light"]) .perk__icon { filter: invert(1) hue-rotate(180deg); }
}

:root[data-theme="dark"] .hero__box--photo {
    --ink:        #F0F0F4;
    --ink-body:   #F0F0F4;
    --ink-muted:  #E8E8F0;
    --indigo-ink: #FFE4EF;
    --collage-bg: #676768;
}

:root[data-theme="dark"] .hero__image { filter: brightness(.42); }
:root[data-theme="dark"] .perk__icon { filter: invert(1) hue-rotate(180deg); }

/* Четыре довода в строку: кружок со значком, подпись под ним.

   Не сетка с равными колонками, а ряд блоков по содержимому — как
   в макете: там подписи разной длины и расстояния между значками
   тоже разные. На узкой колонке ряд переносится сам. */
.perks {
    display: flex;
    flex-wrap: wrap;
    gap: 20px 38px;
    margin: 0 0 24px;
    padding: 0;
    list-style: none;
}
.perk {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    text-align: center;
}

/* Значки — картинки из макета: белый кружок с розовым рисунком
   и мягкой тенью. Размер тот же, что там, — 61x69. */
.perk__icon { width: 61px; height: 69px; }

.perk__label {
    /* Перенос задан в самом тексте, а не отдан ширине блока: в макете
       подписи разбиты по смыслу («Для разных / уровней и возрастов»),
       и ширина блока считается от самой длинной строки. Отдай это
       переносу по месту — и блоки разъедутся на каждой ширине
       по-своему. */
    white-space: pre-line;
    font-size: 13px;
    line-height: 1.4;
    color: var(--ink-muted);
}

.hero__actions { display: flex; gap: 14px; flex-wrap: wrap; }

/* ---------------------------------------------------------------------
   7. Преимущества
   --------------------------------------------------------------------- */
/* Полоса категорий: одна белая коробка, внутри колонки со значками,
   между колонками — тонкие линии. Ровно как в макете.

   Линии рисуются границей соседа, а не отдельными элементами: так их
   не приходится прятать на переносе строки — у первой колонки в ряду
   границы просто нет. */
.cats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr));
    margin: 0;
    padding: 0;
    list-style: none;
    background: var(--canvas);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    overflow: hidden;
}
.cats__item + .cats__item { border-left: 1px solid var(--line); }

.cat {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    gap: 12px;
    height: 100%;
    padding: 28px 14px 24px;
    color: var(--ink-body);
    text-align: center;
    transition: background-color var(--speed) var(--ease), color var(--speed) var(--ease);
}
.cat:hover { background: var(--indigo-soft); color: var(--indigo); }

/* Значок — картинка из макета, а не контур в разметке. Перекрасить
   её при наведении нельзя, да и незачем: подкрашивается подпись,
   а рисунок остаётся собой. */
.cat__icon { width: 62px; height: 63px; object-fit: contain; }
.cat__label { font-size: 13px; line-height: 1.35; }

/* Значки в тёмной теме переворачиваются по светлоте.

   Они вырезаны из макета — тёмный контур по светлой заливке, — и на
   тёмной полосе от них оставались светлые пятна: сначала белые
   квадраты подложки (её убрали из самих файлов), потом светлые
   заливки лампочки и карандаша.

   invert переворачивает светлоту: контур становится светлым, заливка
   уходит в тёмное и сливается с полосой. Получается рисунок линией —
   ровно то, чем значок и был задуман.

   hue-rotate(180deg) возвращает цвета: без него розовый карандаш
   позеленел бы, а жёлтая лампочка посинела. Вдвоём они меняют
   светлоту, оставляя тон.

   Оба пути включения тёмной темы, как и везде: системная настройка
   и кнопка. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .cat__icon { filter: invert(1) hue-rotate(180deg); }
}

:root[data-theme="dark"] .cat__icon { filter: invert(1) hue-rotate(180deg); }


/* ---------------------------------------------------------------------
   8. Каталог
   --------------------------------------------------------------------- */
/* Каталог в две колонки: фильтры слева, товары справа.
   align-items: start — иначе колонка фильтров растягивается на всю
   высоту сетки и «липкость» ей уже не нужна: элемент и так во весь
   экран, прилипать нечему. */
.catalog__layout {
    display: grid;
    grid-template-columns: 250px minmax(0, 1fr);
    gap: 40px;
    align-items: start;
}
.catalog__main { min-width: 0; }

/* Фильтры без плашки и рамки: в колонке они читаются как оглавление,
   а коробка вокруг сделала бы из них ещё один блок наравне с товаром.
   Липкие — чтобы при длинном каталоге не возвращаться наверх. */
.filters {
    position: sticky;
    top: 92px;
    display: grid;
    gap: 22px;
    max-height: calc(100vh - 112px);
    overflow-y: auto;
    /* Место под полосу прокрутки резервируется всегда. Без этого
       колонка в 250 точек при появлении полосы сжимается до 235,
       и то, что стояло впритык, перестаёт помещаться. */
    scrollbar-gutter: stable;
    padding-right: 4px;
    /* Кнопки не упираются в край прокрутки: без поля «Показать»
       и «Сбросить» лежат прямо на границе колонки. */
    padding-bottom: 14px;
}
.filters__search { margin: 0; }
.filters__group { min-width: 0; }
.filters__label {
    display: block;
    margin-bottom: 10px;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--ink-muted);
}
.filters__actions { display: flex; align-items: center; gap: 14px; }

.pills { display: flex; flex-wrap: wrap; gap: 8px; }

/* Категории в колонке — списком, одна под другой: названия у них
   длинные («Рабочий лист», «План урока»), и вперемешку они рвутся
   на строки посреди слова */
.filters__group--category .pills { flex-direction: column; align-items: stretch; gap: 4px; }
.filters__group--category .pill span { justify-content: space-between; width: 100%; }
.pill { position: relative; cursor: pointer; }
.pill input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}
.pill span {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 7px 14px;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--paper);
    font-size: 14px;
    font-weight: 500;
    color: var(--ink-muted);
    transition: all var(--speed) var(--ease);
}
.pill span b { font-weight: 600; font-size: 12px; color: var(--ink-faint); }
.pill:hover span { border-color: var(--pill, var(--indigo)); color: var(--pill, var(--indigo)); }
.pill input:checked + span {
    background: var(--indigo-soft);
    background: color-mix(in srgb, var(--pill, var(--indigo)) 12%, white);
    border-color: var(--pill, var(--indigo));

    /* Цвет чипа берётся из категории (--pill) и на своей же светлой
       подложке не дотягивает до нормы: выбранное «Все» давало 4.28.
       Подмешиваем чёрный — работает для любого оттенка из админки. */
    color: var(--indigo-dark);
    color: color-mix(in srgb, var(--pill, var(--indigo)) 72%, black);
}
.pill input:checked + span b { color: inherit; }
/* Фокус клавиатурой на скрытом input подсвечивает видимую «пилюлю» */
.pill input:focus-visible + span { outline: 3px solid var(--indigo); outline-offset: 2px; }

/* Поля цены тянутся по колонке, а не стоят по 110 точек.

   Жёсткая ширина ровно совпадала с колонкой: 110 + тире + зазоры
   давали 250 при колонке 250. Стоило появиться полосе прокрутки —
   и второе поле обрезалось, а внизу вылезала прокрутка вбок.
   Тянущиеся колонки не зависят ни от ширины колонки, ни от полосы. */
.price-range {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    align-items: center;
    gap: 8px;
}
/* Общий .input--num (110 точек) тут не годится, но он же стоит
   в админке у полей вроде «страниц» — там ширина по числу уместна,
   поэтому переопределяем только внутри ряда цены. */
.price-range .input--num { width: 100%; min-width: 0; }
.price-range__dash { color: var(--ink-faint); }

/* Четыре колонки, как в макете: при колонке 1180 карточка выходит
   около 245 — ровно то, что нарисовано.

   auto-fill с минимумом, а не жёсткие четыре: на планшете колонок
   станет три, потом две, и отдельный медиазапрос на каждую ступень
   не нужен. min() в нижней границе обязателен — голое число не даёт
   колонке сжаться на узком экране и выталкивает страницу вбок. */
.grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(245px, 100%), 1fr));
    gap: 24px;
}

/* Карточка лежит прямо на листе: ни подложки, ни рамки, ни тени.
   Товар здесь — картинка, и всё нарисованное вокруг с ней спорит.
   Отменять эти свойства по одному нельзя: карточка ещё и растёт
   из настроек оформления, поэтому проще не задавать их вовсе.

   Превью вертикальное (3:4): материал — распечатка A4, и обрезать
   лист до альбомного значило показывать половину. */
/* Карточка — белая коробка с полем внутри, как в макете.

   Прежде она была прозрачной и лежала прямо на листе. В макете лист
   тоже белый, но карточка от него отделена: скруглением, полем и
   тонкой линией. Тени по-прежнему нет ни одной — в макете её нет
   ни у одного элемента (все девять фильтров там с радиусом 0.15,
   то есть неразличимы). */
.card {
    display: flex;
    flex-direction: column;
    padding: 16px;
    background: var(--canvas);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    transition: transform var(--speed) var(--ease), border-color var(--speed) var(--ease);
}
.card:hover { transform: translateY(-3px); border-color: var(--coral); }
/* Почти квадрат — пропорции превью из макета (213 на 220) */
.card__media {
    position: relative;
    aspect-ratio: 213 / 220;
    background: var(--paper);
    border-radius: var(--radius);
    overflow: hidden;
}
.card__media-link { display: block; height: 100%; }
.card__image { width: 100%; height: 100%; object-fit: cover; transition: transform .4s var(--ease); }
.card:hover .card__image { transform: scale(1.03); }
.card__media .badge--discount { position: absolute; top: 12px; right: 12px; }

.card__body { padding: 16px 0 12px; flex: 1; }

/* Категория стоит ПОД названием и набрана обычным серым — так
   в макете. Прежде она была над названием, прописными и цветом
   категории: получалась вторая кричащая строка над первой. */
.card__category {
    margin: 6px 0 0;
    font-size: 13px;
    font-weight: 400;
    color: var(--ink-muted);
}
/* Название обычным регистром и заголовочной гарнитурой.
   Прописные уравнивали разноязычные названия в одну строку набора,
   но у этого бренда заглавными не набрано почти ничего, и сетка
   из шести кричащих подписей спорила с картинками. */
/* Ровно две строки: в ряду карточки остаются одной высоты,
   а длинное название под квадратным превью всё равно не читают */
.card__title {
    font-size: 15px;
    font-weight: 600;
    line-height: 1.35;
    letter-spacing: -.01em;
    margin-bottom: 0;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.card__title a { color: var(--ink); }
.card__title a:hover { color: var(--indigo); }
.card__text {
    margin: 0 0 14px;
    font-size: 14px;
    color: var(--ink-muted);
    line-height: 1.55;
    /* Ровно две строки: карточки в ряду остаются одной высоты,
       а под вертикальным превью длинный текст и не нужен */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.card__meta { display: flex; flex-wrap: wrap; gap: 6px; }

/* Подвал без разделительной черты: карточка не коробка, и подводить
   под ней линию значит снова её обрисовать.

   Цена отдельной строкой, под ней две кнопки поровну. В строку они
   на узкой колонке не помещаются и переносятся вразнобой — ряд
   карточек тогда стоит ступеньками. */
.card__footer {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
    padding: 0;
}
/* Одна кнопка во всю ширину — в макете второй нет.
   Быстрый просмотр открывает сама картинка. */
.card__actions { display: grid; gap: 8px; }
.card__actions > .btn, .card__add .btn { width: 100%; }
.card__add { margin: 0; }

/* Розовая полоса «в корзину»: невысокая, светлая, со значком.
   Цвет плашки — из макета, а вот значок там нарисован цветом
   #FEC8DC по #FEB0CC, то есть контраст 1.1: кнопка выглядит пустой
   полосой. Значок сделан тёмным (10.65), цвет плашки сохранён. */
.card__actions .btn--sm {
    min-height: 30px;
    padding: 5px 12px;
    border-radius: var(--radius-sm);
    font-size: 13px;
}

/* Компактный шаблон: меньше текста, плотнее сетка */
.card--compact .card__text { -webkit-line-clamp: 1; }
.card--compact .card__media { aspect-ratio: 4 / 5; }
.card--compact .card__body { padding: 14px 0 8px; }

/* Крупный шаблон: карточка занимает две колонки — так она выделяется
   среди обычных соседей */
.card--featured { grid-column: span 2; }
.card--featured .card__media { aspect-ratio: 4 / 3; }
.card--featured .card__title { font-size: 20px; }

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

   Условие читается так: «сетка, в которой нет ни одной некрупной
   карточки». Проверить это можно только глядя на содержимое, отсюда
   :has() — иначе понадобился бы класс на сетке, а его пришлось бы
   ставить в трёх местах, где она рисуется. */
@media (min-width: 1025px) {
    .grid:not(:has(.card:not(.card--featured))) {
        grid-template-columns: repeat(2, 1fr);
    }

    .grid:not(:has(.card:not(.card--featured))) .card--featured {
        grid-column: span 1;
    }
}

.catalog__more { display: flex; justify-content: center; margin-top: 40px; }

.empty {
    grid-column: 1 / -1;
    padding: 56px 24px;
    text-align: center;
    background: var(--paper);
    border: 1px dashed var(--line);
    border-radius: var(--radius-lg);
}
.empty svg { margin: 0 auto 16px; }
.empty__title { margin: 0 0 6px; font-size: 19px; font-weight: 700; }
.empty__text { margin: 0 0 20px; color: var(--ink-muted); }

/* ---------------------------------------------------------------------
   Два блока в ряд: об авторе и подписка

   Оба ростом по содержимому и одинаковой ширины — в макете это пара,
   а не «главный блок и довесок». Подложки взяты из макета: розовая
   у рассказа, голубая у подписки.
   --------------------------------------------------------------------- */
.duo {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(420px, 100%), 1fr));
    gap: 24px;
    align-items: stretch;
}

.duo__card {
    display: flex;
    align-items: center;
    /* Поля и зазор из макета: 20 и 36 при фотографии в 144 */
    gap: 36px;
    padding: 20px;
    border-radius: var(--radius);
}
.duo__card--pink { background: var(--indigo-soft); }
.duo__card--blue { background: var(--mint-soft); }

.duo__body { min-width: 0; }

/* Кнопка в макете 155x30 — ниже и мельче общей мелкой кнопки.
   Правило местное: тем же классом набраны кнопки админки,
   и уменьшать их ради витрины незачем. */
.duo__body .btn--sm { padding: 6px 14px; font-size: 13px; }

/* Между заголовком и пилюлями — свой зазор: у заголовка снизу 10,
   а тут нужен воздух, иначе пилюли липнут к тексту. */
/* В макете заголовок подписки крупнее рассказа об авторе: 24 против
   20. Это не случайность — приглашение подписаться там главное,
   и держится оно кеглем, а не цветом. Ниже — зазор до пилюль:
   у заголовка снизу 10, а тут нужен воздух. */
.duo__card--blue .duo__title { font-size: 24px; margin-bottom: 18px; }

/* Фотография круглая и не сжимается: во flex-строке картинка без
   flex-shrink: 0 съёживается в овал, когда текст рядом длинный */
.duo__photo { flex-shrink: 0; }
.duo__photo img {
    width: 144px;
    height: 144px;
    border-radius: 50%;
    object-fit: cover;
}

.duo__title {
    font-size: 20px;
    line-height: 1.35;
    margin: 0 0 10px;
    /* Заголовок ломается пополам сам. Раньше перенос стоял тегом <br>
       прямо в разметке: на узком блоке он рвал фразу не там, где надо,
       и подвинуть его было нечем.

       Ширина в знаках, а не в точках: строка в 30 знаков читается
       как заголовок, а растянутая на весь блок — как абзац. Она же
       возвращает «Подписывайтесь и получайте / новые идеи первыми»
       в две строки, как в макете. */
    max-width: 30ch;
    text-wrap: balance;
}
.duo__text {
    margin: 0 0 16px;
    /* 14 на цветной подложке читалось мелко рядом с заголовком в 20 */
    font-size: 15px;
    line-height: 1.6;
    color: var(--ink-body);
    /* Ровняет правый край и не оставляет одно слово на последней
       строке. Браузеры без поддержки просто переносят как раньше. */
    text-wrap: pretty;
}

/* Белые пилюли со ссылками на площадки */
.channels {
    display: flex;
    flex-wrap: wrap;
    gap: 14px;
    margin: 0;
    padding: 0;
    list-style: none;
}
.channel {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    /* Пилюля из макета: 41 в высоту при значке в 30 */
    padding: 5px 14px;
    border-radius: var(--radius);
    background: var(--canvas);
    /* Обводка в макете — у пилюль, а не у самой голубой коробки:
       белое по светло-голубому иначе теряет край. Раньше она стояла
       наоборот, и пилюли выглядели плоскими пятнами. */
    border: 1px solid var(--mint-line);
    color: var(--ink);
    font-size: 14px;
    font-weight: 500;
    transition: transform var(--speed) var(--ease);
}

.channel__icon { width: 30px; height: 30px; flex-shrink: 0; }
.channel:hover { transform: translateY(-2px); color: var(--ink); }

/* ---------------------------------------------------------------------
   10. Отзывы
   --------------------------------------------------------------------- */


/* ---------------------------------------------------------------------
   11. FAQ
   --------------------------------------------------------------------- */
.section--faq { background: var(--paper); border-top: 1px solid var(--line); }
.faq { margin-top: 32px; }
.faq__item {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    margin-bottom: 12px;
    background: var(--paper);
    transition: border-color var(--speed) var(--ease), box-shadow var(--speed) var(--ease);
}
/* Раскрытый вопрос обводится основным цветом темы: прежний
   #A5B4FC был сиреневым и на тёплом фоне выглядел чужим */
.faq__item[open] { border-color: var(--indigo); }
.faq__question {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 18px 22px;
    font-weight: 600;
    font-size: 17px;
    cursor: pointer;
    list-style: none;
}
.faq__question::-webkit-details-marker { display: none; }
.faq__question::after {
    content: '+';
    flex-shrink: 0;
    font-size: 24px;
    font-weight: 400;
    color: var(--indigo);
    line-height: 1;
    transition: transform var(--speed) var(--ease);
}
.faq__item[open] .faq__question::after { transform: rotate(45deg); }
.faq__answer { padding: 0 22px 20px; color: var(--ink-muted); }
.faq__answer p { margin: 0; }

.faq__cta {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 16px;
    margin-top: 36px;
    flex-wrap: wrap;
}
.faq__cta p { margin: 0; color: var(--ink-muted); }

/* ---------------------------------------------------------------------
   12. Подвал
   --------------------------------------------------------------------- */
/* Отдельный токен, а не --ink: подвал тёмный ВСЕГДА, в том числе
   в тёмной теме. Раньше здесь стоял цвет текста, и при смене темы
   фон подвала уезжал вслед за ним. */
.footer {
    background: var(--footer-bg);
    color: var(--footer-ink);
    padding: 56px 0 32px;
    border-top: 1px solid var(--line);
}
.footer__inner {
    display: grid;
    grid-template-columns: 1.6fr 1fr 1fr 1.2fr;
    gap: 40px;
}
/* min-width: 0 снимает у элементов сетки запрет сжиматься ниже
   min-content: без него одна длинная ссылка распирает всю колонку */
.footer__inner > * { min-width: 0; }
.footer__tagline { margin: 12px 0 0; font-size: 15px; color: var(--footer-ink-dim); max-width: 32ch; }
.footer__title { font-size: 14px; font-weight: 700; color: var(--ink); margin-bottom: 14px; letter-spacing: .04em; text-transform: uppercase; }
.footer__col { display: flex; flex-direction: column; gap: 10px; }
.footer__link { color: var(--footer-ink); font-size: 15px; }
.footer__link:hover { color: var(--indigo); }
.footer__social { display: flex; gap: 10px; margin-top: 8px; }
/* Круглые, как в шапке: подвал теперь светлый, и прежняя
   полупрозрачная белая плашка на нём была невидима */
.social {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border: 1px solid var(--line);
    border-radius: 50%;
    background: var(--canvas);
    color: var(--ink-muted);
    transition: color var(--speed) var(--ease), border-color var(--speed) var(--ease);
}
.social:hover { color: var(--indigo); border-color: var(--indigo); }

.footer__bottom {
    display: flex;
    justify-content: space-between;
    gap: 16px;
    margin-top: 48px;
    padding-top: 24px;
    border-top: 1px solid var(--line);
    font-size: 13px;
    color: var(--footer-ink-dim);
    flex-wrap: wrap;
}
.footer__bottom p { margin: 0; }

/* ---------------------------------------------------------------------
   13. Корзина и модальное окно
   --------------------------------------------------------------------- */
.cart { position: fixed; inset: 0; z-index: 100; }
.cart__backdrop {
    position: absolute;
    inset: 0;
    background: rgba(15, 23, 42, .45);
    animation: fade-in var(--speed) var(--ease);
}
.cart__panel {
    position: absolute;
    top: 0;
    right: 0;
    width: min(440px, 100%);
    height: 100%;
    display: flex;
    flex-direction: column;
    background: var(--paper);
    box-shadow: -12px 0 40px rgba(15, 23, 42, .15);
    animation: slide-in .3s var(--ease);
}
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes slide-in { from { transform: translateX(100%); } to { transform: translateX(0); } }

.cart__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 22px 24px;
    border-bottom: 1px solid var(--line);
}
.cart__title { font-size: 20px; }
.cart__close, .modal__close {
    width: 36px;
    height: 36px;
    border: 0;
    border-radius: 10px;
    background: var(--canvas);
    font-size: 22px;
    line-height: 1;
    color: var(--ink-muted);
    cursor: pointer;
}
.cart__close:hover, .modal__close:hover { background: var(--indigo-soft); color: var(--indigo); }

.cart__items { flex: 1; overflow-y: auto; padding: 8px 24px; }
.cart__empty { padding: 48px 24px; text-align: center; }
.cart__empty svg { margin: 0 auto 18px; }
.cart__empty-title { margin: 0 0 6px; font-weight: 700; font-size: 18px; }
.cart__empty-text { margin: 0 0 22px; color: var(--ink-muted); font-size: 15px; }

.cart-item {
    display: flex;
    gap: 14px;
    padding: 16px 0;
    border-bottom: 1px solid var(--line);
}
.cart-item:last-child { border-bottom: 0; }
.cart-item__image { width: 72px; height: 54px; border-radius: 8px; object-fit: cover; flex-shrink: 0; background: var(--indigo-soft); }
.cart-item__info { flex: 1; min-width: 0; }
.cart-item__title { display: block; font-size: 15px; font-weight: 600; color: var(--ink); margin-bottom: 4px; }
.cart-item__title:hover { color: var(--indigo); }
.cart-item__price { font-weight: 700; }
.cart-item__remove {
    align-self: flex-start;
    border: 0;
    background: none;
    color: var(--ink-faint);
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
    padding: 2px 6px;
}
.cart-item__remove:hover { color: var(--danger); }

.cart__foot { padding: 20px 24px 24px; border-top: 1px solid var(--line); background: var(--canvas); }
.promo__label { display: block; font-size: 13px; font-weight: 600; color: var(--ink-muted); margin-bottom: 6px; }
.promo__row { display: flex; gap: 8px; }
.promo__message { margin: 8px 0 0; font-size: 13px; min-height: 18px; }
.promo__message.is-error { color: var(--danger); }
.promo__message.is-ok { color: var(--success); }
.promo--page { margin: 24px 0; }

.cart__totals { margin: 18px 0; }
.cart__row { display: flex; justify-content: space-between; padding: 6px 0; }
.cart__row dt, .cart__row dd { margin: 0; }
.cart__row--discount { color: var(--success); }
.cart__row--total {
    margin-top: 8px;
    padding-top: 14px;
    border-top: 1px solid var(--line);
    font-size: 19px;
    font-weight: 700;
}
.cart__totals--page { max-width: 380px; margin-left: auto; }
.cart__note { margin: 12px 0 0; font-size: 13px; color: var(--ink-muted); line-height: 1.5; }
.cart__checkout { margin-top: 6px; }

.modal { position: fixed; inset: 0; z-index: 110; display: flex; align-items: center; justify-content: center; padding: 24px; }
.modal__backdrop { position: absolute; inset: 0; background: rgba(15, 23, 42, .5); animation: fade-in var(--speed) var(--ease); }
.modal__window {
    position: relative;
    width: min(920px, 100%);
    max-height: 88vh;
    overflow-y: auto;
    background: var(--paper);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    animation: modal-in .25s var(--ease);
}
@keyframes modal-in { from { opacity: 0; transform: translateY(16px) scale(.98); } to { opacity: 1; transform: none; } }
.modal__close { position: absolute; top: 16px; right: 16px; z-index: 2; }
.modal__body { padding: 32px; }

.modal-product { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; }
.modal-product__main img { width: 100%; border-radius: var(--radius); background: var(--indigo-soft); }
.modal-product__thumbs { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
.modal-product__thumbs button {
    width: 68px;
    height: 52px;
    padding: 0;
    border: 2px solid transparent;
    border-radius: 8px;
    overflow: hidden;
    cursor: pointer;
    background: none;
}
.modal-product__thumbs button.is-active { border-color: var(--indigo); }
.modal-product__thumbs img { width: 100%; height: 100%; object-fit: cover; }
.modal-product__title { font-size: 26px; margin: 12px 0; }
.modal-product__desc { font-size: 15px; color: var(--ink-muted); }
.modal-product__buy { display: flex; align-items: center; gap: 16px; margin-top: 22px; flex-wrap: wrap; }

/* ---------------------------------------------------------------------
   14. Внутренние страницы
   --------------------------------------------------------------------- */
.breadcrumbs { display: flex; gap: 8px; margin-bottom: 24px; font-size: 14px; color: var(--ink-faint); flex-wrap: wrap; }
/* Зона нажатия. Ссылка была 23px в высоту при норме 24 (WCAG 2.2,
   критерий 2.5.8) — не хватало ровно пикселя. Исключение для ссылок
   внутри предложения сюда не годится: крошки — это навигация,
   а не текст. */
.breadcrumbs a { color: var(--ink-muted); display: inline-block; padding-block: 3px; }
.breadcrumbs a:hover { color: var(--indigo); }

.product-page__grid { display: grid; grid-template-columns: 1.1fr 1fr; gap: 48px; margin-bottom: 56px; }
.product-page__title { font-size: clamp(26px, 3vw, 36px); margin: 14px 0 12px; }
.product-page__lead { font-size: 17px; color: var(--ink-muted); margin: 0 0 28px; }
.product-page__buy {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
    padding: 22px;
    background: var(--canvas);
    border-radius: var(--radius);
    margin: 28px 0 14px;
    flex-wrap: wrap;
}
.product-page__note { font-size: 14px; color: var(--ink-muted); margin: 0; }

/* Превью страниц материала.

   Не «галерея» и не украшение: для цифрового товара это главный довод.
   Учитель решает по тому, какие внутри задания, а издалека их
   не прочитать — поэтому страница открывается крупно. */
.preview-pages__main { position: relative; }

.preview-pages__zoom {
    display: block;
    width: 100%;
    padding: 0;
    border: 0;
    background: none;
    cursor: zoom-in;
}

/* Высота окна превью не меняется при переключении страниц.

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

   Соотношение сторон берётся от первой страницы (скрипт кладёт его
   в --preview-ratio), остальные вписываются внутрь. Запасное
   значение — вертикальный лист: рабочие листы печатают на A4. */
.preview-pages__zoom img {
    width: 100%;
    aspect-ratio: var(--preview-ratio, 3 / 4);
    object-fit: contain;
    border-radius: var(--radius-lg);
    background: var(--indigo-soft);
    box-shadow: var(--shadow-md);
}

/* Подсказка про увеличение: появляется при наведении, но на телефоне
   наведения нет — там она видна всегда, иначе о ней никто не узнает */
.preview-pages__hint {
    position: absolute;
    right: 14px;
    bottom: 14px;
    padding: 6px 12px;
    border-radius: var(--radius);
    background: var(--badge-plate);
    color: var(--ink);
    font-size: 13px;
    font-weight: 600;
    box-shadow: var(--shadow-sm);
    opacity: 0;
    transition: opacity var(--speed) var(--ease);
}

.preview-pages__zoom:hover .preview-pages__hint,
.preview-pages__zoom:focus-visible .preview-pages__hint { opacity: 1; }

@media (hover: none) {
    .preview-pages__hint { opacity: 1; }
}

.preview-pages__label {
    margin: 16px 0 8px;
    font-size: 14px;
    font-weight: 600;
    color: var(--ink-muted);
}

.preview-pages__thumbs { display: flex; gap: 10px; flex-wrap: wrap; }

.preview-pages__thumb {
    position: relative;
    width: 96px;
    height: 72px;
    padding: 0;
    border: 2px solid var(--line);
    border-radius: 10px;
    overflow: hidden;
    background: none;
    cursor: pointer;
}

.preview-pages__thumb.is-active { border-color: var(--indigo); }
.preview-pages__thumb img { width: 100%; height: 100%; object-fit: cover; }

/* Номер страницы: без него непонятно, какая по счёту сейчас открыта */
.preview-pages__num {
    position: absolute;
    left: 4px;
    bottom: 4px;
    min-width: 18px;
    padding: 0 5px;
    border-radius: 6px;
    background: var(--badge-plate);
    color: var(--ink);
    font-size: 11px;
    font-weight: 700;
    line-height: 18px;
    text-align: center;
}

/* Окно с крупной страницей */
.zoom {
    position: fixed;
    inset: 0;
    z-index: 120;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: rgba(21, 21, 26, .88);
    cursor: zoom-out;
    overflow: auto;
}

.zoom__image {
    /* Именно width, а не только max-width: у превью бывает своя
       небольшая натуральная величина, и с одним max-width картинка
       открывалась того же размера, что на странице. Увеличение,
       которое не увеличивает, — обман. */
    width: min(100%, 1100px);
    max-height: 92vh;
    object-fit: contain;
    border-radius: var(--radius);
    background: var(--paper);
    cursor: default;
}

.zoom__close {
    position: fixed;
    top: 16px;
    right: 16px;
    width: 44px;
    height: 44px;
    border: 0;
    border-radius: 50%;
    background: rgba(255, 255, 255, .16);
    color: #fff;
    font-size: 28px;
    line-height: 1;
    cursor: pointer;
}

.zoom__close:hover { background: rgba(255, 255, 255, .28); }

.specs { margin: 0 0 8px; }
.specs__row { display: flex; justify-content: space-between; gap: 20px; padding: 11px 0; border-bottom: 1px solid var(--line); }
.specs__row dt { color: var(--ink-muted); margin: 0; }
.specs__row dd { margin: 0; font-weight: 600; text-align: right; }
.specs__row--total { font-size: 18px; border-bottom: 0; }

.related { margin-top: 64px; }

.cart-list { margin-bottom: 24px; }
.cart-list__item { display: flex; align-items: center; gap: 16px; padding: 16px 0; border-bottom: 1px solid var(--line); }
.cart-list__image { width: 88px; height: 66px; border-radius: 10px; object-fit: cover; background: var(--indigo-soft); }
.cart-list__info { flex: 1; }
.cart-list__title { display: block; font-weight: 600; color: var(--ink); }
.cart-list__price { color: var(--ink-muted); }

.order {
    padding: 24px;
    margin-bottom: 20px;
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
}
.order__head { display: flex; justify-content: space-between; gap: 20px; margin-bottom: 18px; flex-wrap: wrap; }
.order__number { font-size: 18px; }
.order__date { margin: 4px 0 0; font-size: 14px; color: var(--ink-muted); }
.order__meta { display: flex; align-items: center; gap: 14px; }
.order__sum { font-weight: 700; font-size: 18px; }
.order__item { display: flex; align-items: center; gap: 14px; padding: 12px 0; border-top: 1px solid var(--line); }
.order__image { width: 72px; height: 54px; border-radius: 8px; object-fit: cover; background: var(--indigo-soft); }
.order__item-info { flex: 1; }
.order__item-title { display: block; font-weight: 600; font-size: 15px; }
.order__item-price { font-size: 14px; color: var(--ink-muted); }
.order__expired, .order__pending { font-size: 13px; color: var(--ink-muted); }

.status {
    display: inline-flex;
    padding: 4px 11px;
    border-radius: var(--radius);
    font-size: 12px;
    font-weight: 600;
    background: var(--canvas);
    color: var(--ink-muted);
    white-space: nowrap;
}
/* Плашки статусов: пары под обе темы. Пастельные подложки подбирались
   под белый лист и на тёмном фоне светятся фонариком, а «Не настроена»
   вдобавок была нечитаемой — приглушённый серый по светлой плашке,
   которая тему не меняла, давал 2.34. Эти же плашки видит покупатель
   в своих заказах, поэтому цвета живут здесь, а не в админке. */
.status--paid, .status--active {
    background: light-dark(#DCFCE7, #14532D);
    color:      light-dark(#15803D, #86EFAC);
}
.status--delivered {
    background: var(--mint-soft);
    color:      var(--mint-ink);
}
.status--completed {
    background: var(--indigo-soft);
    color:      var(--indigo-dark);
}
.status--pending {
    background: light-dark(#FEF3C7, #422006);
    color:      light-dark(#B45309, #FCD34D);
}
.status--refunded, .status--canceled, .status--archived {
    background: light-dark(#FEE2E2, #450A0A);
    color:      light-dark(#B91C1C, #FCA5A5);
}
.status--hidden {
    background: light-dark(#EFE8E3, #3B2F28);
    color:      light-dark(#5C4636, #DCCDC3);
}

.result { text-align: center; padding: 48px 24px 32px; }
.result__icon { display: inline-flex; margin-bottom: 18px; }
.result--success .result__icon { color: var(--success); }
.result--error .result__icon { color: var(--danger); }
.result--pending .result__icon { color: #B45309; }
.result__title { font-size: clamp(24px, 3vw, 32px); margin-bottom: 12px; }
.result__text { margin: 0 auto; max-width: 52ch; color: var(--ink-muted); font-size: 16px; }
.result__actions { display: flex; justify-content: center; gap: 14px; margin-top: 32px; flex-wrap: wrap; }

.download-list { margin: 0 auto; max-width: 620px; }
.download-list__item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
    padding: 18px 20px;
    margin-bottom: 12px;
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--radius);
}
.download-list__title { display: block; font-weight: 600; }
.download-list__meta { font-size: 14px; color: var(--ink-muted); }

/* Страницы ошибок */
.error-page { display: flex; align-items: center; justify-content: center; min-height: 100vh; }
.error { text-align: center; padding: 40px 24px; }
/* --indigo-soft — цвет ПОДЛОЖКИ, и цифра, набранная им, не читалась
   ни в одной теме: 1.07 на светлом холсте, 1.22 на тёмном. Смесь
   с холстом сохраняет замысел (крупный мягкий номер), но остаётся
   видимой: 84 пикселя — крупный текст, ему нужно 3. */
.error__code {
    font-size: 84px;
    font-weight: 800;
    color: color-mix(in srgb, var(--indigo) 75%, var(--canvas));
    margin: 0;
    line-height: 1;
}
.error__title { font-size: 28px; margin: 8px 0 12px; }
.error__text { color: var(--ink-muted); margin: 0 auto 28px; max-width: 44ch; }

/* ---------------------------------------------------------------------
   15. Служебные состояния
   --------------------------------------------------------------------- */
.flash-stack {
    position: fixed;
    top: var(--header-h);
    right: 24px;
    z-index: 120;
    display: flex;
    flex-direction: column;
    gap: 10px;
    max-width: 380px;
}
.flash {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 14px 16px;
    border-radius: var(--radius);
    background: var(--paper);
    box-shadow: var(--shadow-lg);
    border-left: 4px solid var(--indigo);
    font-size: 15px;
    animation: slide-in-right .25s var(--ease);
}
@keyframes slide-in-right { from { opacity: 0; transform: translateX(20px); } to { opacity: 1; transform: none; } }
.flash--success { border-left-color: var(--success); }
.flash--error { border-left-color: var(--danger); }
/* Крестик был 13×20 — меньше нормы WCAG 2.2 в 24×24 (критерий 2.5.8).
   Промахнуться по нему пальцем на телефоне было легче, чем попасть,
   а сообщение об ошибке — ровно то место, где человек уже раздражён.
   Сам знак остался прежнего размера, выросла область нажатия. */
.flash__close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 28px;
    height: 28px;
    margin: -4px -6px -4px 0;
    border: 0;
    border-radius: var(--radius-sm);
    background: none;
    color: var(--ink-faint);
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
    padding: 0;
}
.flash__close:hover { color: var(--ink); }

/* Появление блоков при прокрутке. Ставится скриптом; без JS
   элементы просто видны — data-reveal сам по себе ничего не прячет. */
.is-revealable { opacity: 0; transform: translateY(20px); }
.is-revealed {
    opacity: 1;
    transform: none;
    transition: opacity .55s var(--ease), transform .55s var(--ease);
}

.is-loading { opacity: .55; pointer-events: none; }

body.is-locked { overflow: hidden; }

/* ---------------------------------------------------------------------
   16. Адаптив
   --------------------------------------------------------------------- */
/* Меню под бургер с 1024, а не с 768. Между ними знак, четыре
   пункта и шесть кнопок в строку не помещаются — замерено: при 900
   подписи под знаком доставалось 199 пикселей из нужных 210. */
@media (max-width: 1024px) {
    .nav {
        position: fixed;
        inset: var(--header-h) 0 auto;
        flex-direction: column;
        gap: 0;
        padding: 12px;
        background: var(--paper);
        border-bottom: 1px solid var(--line);
        box-shadow: var(--shadow-lg);
        transform: translateY(calc(-100% - 96px));
        transition: transform .28s var(--ease);
    }
    .nav.is-open { transform: translateY(0); }
    .nav__link { padding: 14px 16px; font-size: 16px; }
    .burger { display: flex; }

    .hero__title { max-width: none; }
    .cats { grid-template-columns: repeat(3, minmax(0, 1fr)); }

    /* Фильтры возвращаются полосой над сеткой: колонка в 250px
       съедает у планшета треть ширины, а карточек в ряду остаётся две.
       И липкость снимаем — на невысоком экране прилипшая полоса
       фильтров закрывала бы половину товара. */
    .catalog__layout { grid-template-columns: 1fr; gap: 28px; }
    .filters {
        position: static;
        max-height: none;
        overflow: visible;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 20px 28px;
        padding: 20px;
        background: var(--paper);
        border-radius: var(--radius);
    }
    .filters__search { grid-column: 1 / -1; }
    .filters__actions { grid-column: 1 / -1; }
    .filters__group--category .pills { flex-direction: row; align-items: flex-start; gap: 8px; }
    .filters__group--category .pill span { width: auto; }

    .grid { grid-template-columns: repeat(2, 1fr); }
    .card--featured { grid-column: span 2; }
    /* Пара нижних блоков на планшете: розовый складывается в колонку,
       иначе фотография 144px и текст рядом не помещаются */
    .duo__card { flex-direction: column; align-items: flex-start; text-align: left; }
    .product-page__grid { grid-template-columns: 1fr; gap: 32px; }
    .footer__inner { grid-template-columns: 1fr 1fr; gap: 32px; }
    .modal-product { grid-template-columns: 1fr; }
}

@media (max-width: 768px) {
    .section { padding: 56px 0; }

    /* Меню и бургер описаны выше, в запросе 1024: с этой ширины
       они уже такие. Повторять их здесь значит завести вторую копию,
       которая однажды разойдётся с первой. */
    .account__name { display: none; }

    /* Подпись под знаком прячется целиком, а не режется. Замерено:
       при ширине окна 375 ей нужно 210 пикселей, а достаётся 90 —
       на экране оставалось «идеи и мат…». Обрезанный до трети девиз
       хуже отсутствующего. */
    .logo__tagline { display: none; }

    /* Кружки соцсетей уходят с телефона. Их там три штуки рядом
       с входом, темой, корзиной и бургером, и на всё это не хватает
       строки: при 414 названию магазина оставалось 52 пикселя из
       нужных 132 — на экране висело «teachm…».

       Потеря небольшая: те же ссылки есть в подвале и в блоке
       подписки на главной. Название магазина есть только здесь. */
    .header__social { display: none; }
    .btn--vk span { display: none; }
    .btn--vk { padding: 12px 14px; }

    /* Одна колонка — только на телефонах. На планшете 768px
       по макету две: карточка шириной 350px там читается,
       а во всю ширину экрана выглядит растянутой. */
    /* .grid переехал в запрос 640px ниже */
    .card--featured { grid-column: span 1; }

    .filters { grid-template-columns: 1fr; padding: 18px; }

    /* Первый экран на планшете: коробка в одну колонку.
       Поля — только той, что без фотографии: у коробки с коллажем
       их нет, картинка занимает её целиком. */
    .hero__box:not(.hero__box--photo) { grid-template-columns: 1fr; gap: 28px; padding: 32px; }

    .flash-stack { top: 84px; right: 12px; left: 12px; max-width: none; }
    .modal__body { padding: 22px; }
    .footer__inner { grid-template-columns: 1fr; }
}

@media (max-width: 480px) {
    :root { --gutter: 16px; }
    :root { --header-h: 64px; }
    .header__inner { gap: 12px; }
    .card__footer { flex-direction: column; align-items: stretch; }
    .card__actions { justify-content: space-between; }
    .btn--lg { padding: 13px 22px; }
    .hero__actions .btn { width: 100%; }
}

/* Уважение к системной настройке «меньше движения»: анимации
   выключаются целиком, а не «почти» */
@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }

    *, *::before, *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
    }

    .is-revealable { opacity: 1; transform: none; }
}

/* Печать: интерфейс не нужен, содержимое нужно */
@media print {
    .header, .footer, .cart, .modal, .flash-stack, .filters, .catalog__more { display: none !important; }
    body { background: #fff; }
    .section { padding: 16px 0; }
}

/* =====================================================================
   Юридические элементы: уведомление о cookie, согласия, «Мои данные»
   ===================================================================== */

/* --- Уведомление о файлах cookie ------------------------------------ */
.cookie {
    position: fixed;
    left: 16px;
    right: 16px;
    bottom: 16px;
    z-index: 60;
    /* Тёмная плашка в обеих темах, и цвет у неё теперь свой.

       Дважды одна и та же беда. Сперва здесь стояла var(--c-text) —
       переменной с таким именем в файле нет ни одной, объявление
       отбрасывалось, и белый текст баннера оставался на белой
       странице. Потом подставили var(--footer-bg), и всё работало,
       пока подвал был тёмным; стоило ему по макету стать белым —
       белый текст снова лёг на белое.

       Мораль: если плашка обязана быть тёмной сама по себе, цвет
       у неё должен быть свой, а не одолженный у соседа. */
    background: var(--plate-dark);
    color: #fff;
    border-radius: 16px;
    box-shadow: 0 18px 40px rgba(15, 23, 42, .28);
    animation: cookie-in .35s ease-out both;
}

@keyframes cookie-in {
    from { opacity: 0; transform: translateY(16px); }
    to   { opacity: 1; transform: none; }
}

.cookie__inner {
    display: flex;
    align-items: center;
    gap: 20px;
    max-width: 1120px;
    margin: 0 auto;
    padding: 16px 20px;
}

.cookie__text {
    margin: 0;
    font-size: 14px;
    line-height: 1.55;
    color: rgba(255, 255, 255, .88);
}

.cookie__text a { color: #fff; text-decoration: underline; }

.cookie__accept { flex-shrink: 0; }

@media (max-width: 640px) {
    .cookie__inner { flex-direction: column; align-items: stretch; gap: 12px; }
}

/* --- Галочки согласия ------------------------------------------------ */
.consent {
    display: grid;
    gap: 10px;
    margin: 16px 0;
}

.consent__item {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    font-size: 13px;
    line-height: 1.5;
    color: var(--ink-body);
    cursor: pointer;
}

.consent__item input[type="checkbox"] {
    flex-shrink: 0;
    width: 18px;
    height: 18px;
    margin-top: 1px;
    accent-color: var(--indigo);
    cursor: pointer;
}

.consent__required { color: var(--danger); font-weight: 700; }
.consent__optional { color: var(--ink-muted); }

/* Незаполненный реквизит в тексте документа — должно быть заметно */
.legal-missing {
    background: #FEF3C7;
    color: #92400E;
    padding: 1px 6px;
    border-radius: 6px;
    font-weight: 600;
}

/* --- Реквизиты и возрастной знак в подвале --------------------------- */
.footer__requisites {
    margin: 0 0 10px;
    font-size: 13px;
    line-height: 1.6;
    color: var(--footer-ink-dim);
}

.footer__age {
    display: inline-block;
    margin-left: 8px;
    padding: 1px 7px;
    border: 1px solid currentColor;
    border-radius: 6px;
    font-size: 12px;
    font-weight: 600;
    opacity: .75;
}

/* --- Страница «Мои данные» ------------------------------------------ */
.datalist { margin: 0 0 18px; }

.datalist__row {
    display: flex;
    justify-content: space-between;
    gap: 16px;
    padding: 10px 0;
    border-bottom: 1px solid var(--line);
}

.datalist__row:last-child { border-bottom: 0; }
.datalist__row dt { color: var(--ink-muted); font-size: 14px; }
.datalist__row dd { margin: 0; font-weight: 500; text-align: right; }

.form-inline {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 14px;
}

.form-stack { display: grid; gap: 12px; max-width: 340px; }

/* Опасная зона — «отозвать согласие и удалить данные».

   Цвета были вписаны числами и про тёмную тему не знали: #B91C1C
   на почти чёрном фоне давал 2.93 при норме 3 для крупного текста.
   Через токены палитры обе темы считаются сами: в светлой #DC2626
   даёт 4.53, в тёмной #FCA5A5 — 9.9. */
.panel--danger { border-color: light-dark(#FECACA, #7F1D1D); }
.panel--danger .panel__title { color: var(--danger); }
.panel__divider { border: 0; border-top: 1px solid var(--line); margin: 20px 0; }

/* ---------------------------------------------------------------------
   Таблицы

   Живут здесь, а не в admin.css, хотя выглядят «админскими». Причина
   простая: теми же классами размечена «История согласий» в личном
   кабинете покупателя, а витрина admin.css не подключает. Правила
   лежали только там, и на телефоне обёртка без overflow-x не держала
   таблицу — вбок уезжала вся страница, 33 пикселя при ширине 375.
   Сама таблица при этом рисовалась браузерным умолчанием.

   Порядок важен: уплотнение .table--compact идёт НИЖЕ основы.
   Специфичность у «.table th» и «.table--compact th» одинаковая,
   и спор между ними решает порядок в файле.
   --------------------------------------------------------------------- */
/* Горизонтальная прокрутка внутри обёртки, а не всей страницы */
.table-wrap {
    overflow-x: auto;
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--radius);
}
.table { width: 100%; border-collapse: collapse; font-size: 14px; }
.table th {
    padding: 12px 14px;
    text-align: left;
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--ink-muted);
    border-bottom: 1px solid var(--line);
    white-space: nowrap;
}
.table td { padding: 12px 14px; border-bottom: 1px solid var(--line); vertical-align: middle; }
.table tr:last-child td { border-bottom: 0; }
.table tbody tr:hover { background: var(--canvas); }
.table small { display: block; color: var(--ink-muted); font-size: 12px; }

.table--compact th,
.table--compact td { padding: 8px 10px; font-size: 13px; }

/* =====================================================================
   17. Доступность и современные режимы
   Добавлено по итогам аудита фронтенда: замеры контраста, зон
   нажатия и поведения на телефоне.
   ===================================================================== */

/* --- Заголовок карточки как цель нажатия ----------------------------
   Ссылка в <h3> имела высоту 20px при минимуме 24px по WCAG 2.2
   (критерий 2.5.8). Отступ по вертикали добивает зону до нормы,
   не сдвигая вёрстку: inline-block поверх строчного элемента
   высоту строки не меняет. */
.card__title a {
    display: inline-block;
    padding-block: 2px;
}

/* --- Первый экран на телефоне ---------------------------------------
   Прежде иллюстрация стояла отдельной колонкой и на телефоне
   занимала почти весь экран: заголовок начинался на 612-й точке
   при высоте 812 — человек видел картинку и не видел, что ему
   предлагают. Теперь сначала идёт текст, а коллаж под ним.

   Поля — коробке без фотографии: у коробки с коллажем текст стоит
   по общей сетке страницы, а не с двойным отступом. */
@media (max-width: 640px) {
    .hero__box:not(.hero__box--photo) { padding: 28px 22px; gap: 26px; }

    /* Доводы — двумя парами. В ряд они на телефоне не встают, а сами
       перенеслись бы как три плюс один: блоки разной ширины, и место
       кончается посередине. */
    .perks {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 18px;
        justify-items: center;
    }
}

/* --- Уведомление о cookie -------------------------------------------
   Раньше баннер перекрывал кнопку «Перейти в каталог»: главный
   призыв к действию оказывался под плашкой при первом же заходе.
   Стал компактнее, прижат к правому краю на широких экранах и
   отодвигает содержимое снизу, пока показан. */
.cookie {
    left: auto;
    right: 16px;
    max-width: 560px;
}

.cookie__inner { padding: 14px 18px; }
.cookie__text  { font-size: 13px; }

/* :has() поддерживают все актуальные браузеры; там, где его нет,
   баннер просто останется поверх — как было. */
body:has(#cookie-banner) { padding-bottom: 96px; }

@media (max-width: 640px) {
    .cookie { left: 12px; right: 12px; bottom: 12px; max-width: none; }
    body:has(#cookie-banner) { padding-bottom: 150px; }
}

/* --- Переключение темы ------------------------------------------------

   Три состояния, и порядок правил здесь важен:

     нет data-theme  — как в системе;
     data-theme=dark — тёмная принудительно;
     data-theme=light — светлая принудительно, даже если система тёмная.

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

   Сами цвета здесь не повторяются: каждый токен уже описан парой
   значений через light-dark(), и достаточно переключить color-scheme.
   Раньше тёмная палитра была отдельной копией всего набора — две копии
   неизбежно расходятся, а медиазапрос вдобавок нельзя отменить кнопкой.
   -------------------------------------------------------------------- */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) { color-scheme: dark; }
}

:root[data-theme="dark"]  { color-scheme: dark; }
:root[data-theme="light"] { color-scheme: light; }

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

/* Бейдж категории: цвет задаёт администратор под светлую тему.
   На светлом фоне его затемняем, на тёмном — осветляем. */
.badge--category {
    background: color-mix(in srgb, var(--badge, var(--indigo)) 14%, white);
    background: light-dark(
        color-mix(in srgb, var(--badge, var(--indigo)) 14%, white),
        color-mix(in srgb, var(--badge, var(--indigo)) 22%, #101014)
    );
    color: light-dark(
        color-mix(in srgb, var(--badge, var(--indigo)) 62%, black),
        color-mix(in srgb, var(--badge, var(--indigo)) 65%, white)
    );
}

/* Категория под карточкой больше не красится цветом категории:
   в макете это обычная серая строка. Цвет владельца остался там,
   где он и нужен, — на плашках .badge--category и на фильтрах.

   Правило было именно здесь, ниже по файлу, и перебивало описание
   .card__category выше. Специфичность одинаковая, спор решает
   порядок — первая правка молча не действовала. */

/* Выбранный фильтр — та же история */
.pill input:checked + span {
    background: light-dark(
        color-mix(in srgb, var(--pill, var(--indigo)) 12%, white),
        color-mix(in srgb, var(--pill, var(--indigo)) 22%, #101014)
    );
    color: light-dark(
        color-mix(in srgb, var(--pill, var(--indigo)) 72%, black),
        color-mix(in srgb, var(--pill, var(--indigo)) 55%, white)
    );
}

/* Наклейки на стопке: обе пары уже описаны через light-dark()
   в самих токенах (--indigo-ink, --mint-ink, --*-soft), поэтому
   отдельная тёмная копия им больше не нужна. */

/* Длинный текст из редактора */
.prose { color: var(--ink-body); }

/* Иллюстрации-заглушки рисовались под белый лист.

   Фотографии первого экрана здесь больше нет: она лежит фоном
   под плёнкой, и приглушать её отдельно незачем. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .card__image[src$=".svg"] { opacity: .86; }
}
:root[data-theme="dark"] .card__image[src$=".svg"] { opacity: .86; }

/* --- Печать ----------------------------------------------------------
   Учителя печатают страницы с описанием материала. Убираем всё,
   что на бумаге бессмысленно. */
@media print {
    .header, .footer, .cookie, .cart, .modal, .btn, .filters, .skip-link { display: none !important; }
    body { background: #fff; color: #000; padding: 0; }
    .section { padding: 12px 0; }
    a[href^="/"]::after { content: " (" attr(href) ")"; font-size: 11px; color: #555; }
}

/* --- Ссылки в подвале ------------------------------------------------
   Прежде подвал был тёмным в обеих темах, и цвет ссылок был задан
   числом (#C7D2FE) — фирменный розовый на чёрном давал 3.33. Теперь
   подвал светлый в светлой теме и тёмный в тёмной, поэтому цвет
   снова берётся из палитры и следует за темой сам. */
.footer a:not(.btn):not(.social) { color: var(--indigo-ink); }
.footer a:not(.btn):not(.social):hover { color: var(--indigo-dark); }
.footer__link { color: var(--footer-ink); }
.footer__link:hover { color: var(--indigo); }

/* Каталог: одна колонка начинается с телефонов, а не с планшетов */
@media (max-width: 640px) {
    .grid { grid-template-columns: 1fr; }
}

/* =====================================================================
   18. Секция с картинкой на фоне
   Фон задаётся из админки (Дизайн витрины -> Секции). Поверх картинки
   лежит затемняющая плёнка, поэтому текст здесь всегда светлый:
   иначе тёмные заголовки пропадали бы на тёмной фотографии.
   ===================================================================== */
.section--on-image {
    position: relative;
    color: #F7F2EE;
}

.section--on-image .section__title,
.section--on-image h2,
.section--on-image h3,
.section--on-image .hero__title { color: #fff; }

.section--on-image .section__lead,
.section--on-image .section__count,
.section--on-image .hero__subtitle,
.section--on-image .perk__label,
.section--on-image .muted { color: #E0D2C8; }

.section--on-image .eyebrow { color: #E8B99B; }

/* Карточки поверх фотографии остаются светлыми «листами» — так
   каталог не сливается с фоном */
.section--on-image .card,
.section--on-image .filters,
.section--on-image .faq__item { color: var(--ink); }

.section--on-image .btn--ghost {
    background: rgba(255, 255, 255, .12);
    border-color: rgba(255, 255, 255, .45);
    color: #fff;
}
.section--on-image .btn--ghost:hover {
    background: rgba(255, 255, 255, .2);
    border-color: #fff;
    color: #fff;
}

/* Переключение темы: на один кадр снимаем все переходы.

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

   Вторая — Chrome не пересчитывает light-dark() у свойств, на
   которых висит transition, когда меняется только color-scheme.
   Фон панели перекрашивался, а цвет ссылок застревал на прежнем:
   светло-индиговый по белому давал 2.4 при норме 4.5. Снятый
   переход заставляет пересчитать свойство честно. Проверено на
   Chrome 148: без правила цвет ссылок остаётся старым до перехода
   на другую страницу. */
:root.theme-switching,
:root.theme-switching *,
:root.theme-switching *::before,
:root.theme-switching *::after {
    transition: none !important;
}

/* --- Кнопка переключения темы --------------------------------------- */
.theme-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    flex-shrink: 0;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--paper);
    color: var(--ink-muted);
    cursor: pointer;
    transition: color var(--speed) var(--ease), border-color var(--speed) var(--ease);
}

.theme-toggle:hover { color: var(--indigo); border-color: var(--indigo); }
.theme-toggle__icon { display: flex; }

/* Предложение войти — под формой оформления, а не вместо неё.

   Вход больше не преграда: покупка проходит и без него. Поэтому блок
   выглядит как выгода, а не как требование, и стоит после кнопки
   оплаты — иначе он снова читался бы как обязательный шаг. */
.cart__account {
    margin-top: 20px;
    padding-top: 20px;
    border-top: 1px solid var(--line);
}

.cart__account-text {
    margin: 0 0 12px;
    font-size: 14px;
    line-height: 1.55;
    color: var(--ink-muted);
}

/* Пояснение под главной строкой результата: тише её, но читаемо */
.result__text--sub {
    margin-top: 12px;
    font-size: 15px;
    color: var(--ink-muted);
}

/* =====================================================================
   Отзывы покупателей

   Показываются все, включая низкие оценки: витрина, где все отзывы
   восторженные, выглядит подделкой и не убеждает. Пометка о покупке
   стоит рядом с именем — именно она отличает эти отзывы от прежних,
   которые владелец писал себе сам.
   ===================================================================== */
.reviews { margin-top: 64px; }

.reviews__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 20px;
    flex-wrap: wrap;
}

.reviews__average { display: flex; align-items: baseline; gap: 10px; margin: 0; }
.reviews__score { font-size: 28px; font-weight: 800; letter-spacing: -.02em; }
.reviews__count { font-size: 15px; color: var(--ink-muted); }

.reviews__empty,
.reviews__note { margin: 0 0 20px; color: var(--ink-muted); line-height: 1.6; }

.reviews__list { display: grid; gap: 18px; margin-top: 20px; }

.review {
    padding: 20px 22px;
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--radius);
}

.review__head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.review__author { font-weight: 700; }

/* Пометка о покупке — тихая, но заметная: это довод, а не украшение.

   Сам --success по своей же светлой подложке даёт 3.79 при норме 4.5:
   зелёный на бледно-зелёном. Поэтому надпись доводится подмешиванием —
   к чёрному на светлой теме, к белому на тёмной. */
.review__verified {
    padding: 3px 10px;
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--success) 14%, var(--paper));
    color: var(--success);
    color: light-dark(
        color-mix(in srgb, var(--success) 75%, black),
        color-mix(in srgb, var(--success) 70%, white)
    );
    font-size: 12px;
    font-weight: 600;
}

/* Звёзды несут оценку, а не украшают, — значит обязаны читаться.
   Прежний янтарный #F59E0B давал на белом 1.95. */
.review__rating {
    margin: 8px 0 10px;
    font-size: 17px;
    color: #9A4A07;
    color: light-dark(#9A4A07, #FBBF24);
    letter-spacing: 2px;
}
.review__text { margin: 0 0 10px; line-height: 1.65; color: var(--ink-body); }
.review__date { font-size: 13px; color: var(--ink-faint); }

/* --- Форма отзыва ---------------------------------------------------- */
.review-form__product { margin: 0 0 8px; font-size: 18px; font-weight: 700; }
.review-form__lead { margin: 0 0 24px; color: var(--ink-muted); line-height: 1.6; }
.review-form { display: grid; gap: 20px; }
.review-form__note { margin: 0; font-size: 13px; color: var(--ink-muted); line-height: 1.55; }

.rating-pick { display: flex; gap: 10px; flex-wrap: wrap; border: 0; padding: 0; margin: 0; }
.rating-pick legend { margin-bottom: 10px; }

.rating-pick__item { position: relative; }
.rating-pick__item input { position: absolute; opacity: 0; width: 0; height: 0; }

.rating-pick__label {
    display: inline-flex;
    align-items: center;
    min-height: 40px;
    padding: 8px 14px;
    border: 2px solid var(--line);
    border-radius: var(--radius-sm);
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
}

.rating-pick__item input:checked + .rating-pick__label {
    border-color: var(--indigo);
    background: var(--indigo-soft);
}

.rating-pick__item input:focus-visible + .rating-pick__label {
    outline: 3px solid var(--indigo);
    outline-offset: 2px;
}

/* =====================================================================
   18. Самые узкие телефоны (до 380px)

   Блок стоит последним намеренно. Поставленный выше, он частью
   не работал: .theme-toggle описан ниже по файлу, специфичность
   у правил одинаковая, а медиазапрос её не повышает — спор решает
   порядок в файле, и побеждало правило снизу. Кнопка темы оставалась
   40 пикселей при соседях по 36.

   Числа здесь считаны, а не подобраны. При ширине окна 320 на строку
   шапки остаётся 288 (минус поля по 16). Логотип целиком — значок 28,
   зазор 10 и слово «TeachMe» 83 — занимает 121; на четыре кнопки
   остаётся 157. При 44×44 с зазором 12 они просят 208, и лишнее
   отбиралось у логотипа: от имени магазина оставалось «TeachM…».
   36 на кнопку и зазор 4 дают ровно 156.

   Норма WCAG 2.2 для зоны нажатия — 24×24, так что 36 с запасом.
   ===================================================================== */
@media (max-width: 380px) {
    /* Поля страницы уже, чем на остальных телефонах. Считано: при 320
       на строку шапки остаётся 288, а знаку (134), зазору и четырём
       кнопкам по 36 нужно 300. Поле 12 вместо 16 даёт 296, кнопка 34
       вместо 36 — ещё восемь. Норма WCAG 2.2 — 24, запас остаётся. */
    :root { --gutter: 12px; }

    .header__inner   { gap: 10px; }
    .header__actions { gap: 4px; }

    .cart-button,
    .burger,
    .theme-toggle { width: 34px; height: 34px; }

    .burger  { padding: 0 8px; }
    .btn--vk { padding: 8px 9px; }

    /* Имя магазина не сжимается и не сокращается: это единственное
       слово на странице, которое обязано читаться целиком. Зато
       на самых узких экранах оно набирается мельче — считано:
       при 320 на строку остаётся 288, а знаку и четырём кнопкам
       при кегле 20 нужно 296. */
    .logo__text { flex-shrink: 0; }

    /* У вошедшего в шапке ещё и аватар — строка становится на 13
       пикселей длиннее, чем у гостя, и при 320 этого уже не хватало.
       Подпись с именем тут и так скрыта, поле вокруг аватара лишнее. */
    .account__toggle { padding: 4px; }

    /* Две колонки преимуществ при 320px не сжимаются ниже своего
       содержимого — padding 24+24 плюс самое длинное слово держат
       150 пикселей при доступных 134. Именно они, а не шапка,
       тянули страницу вбок. В колонке 134 под текст остаётся 86:
       столько всё равно не прочитать. */
    .cats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}



