/*
 * Примитивы публичной части — второй шаг удушения темы.
 *
 * Всё построено на токенах из tokens.css. Ни одного литерального цвета,
 * размера или отступа: это и есть смысл слоя.
 *
 * Классы с приставкой `ov-` — она отделяет свой слой от темы и делает
 * состояние переезда видимым: страница переехала, когда в её разметке
 * остались только `ov-*` (плюс `wow` для анимации появления).
 *
 * Чего здесь намеренно нет:
 *
 *  - **сетки из двенадцати колонок.** Она нужна была, когда не было ни grid,
 *    ни flex. Сегодня «две колонки, на узком экране одна» — это одна строка
 *    CSS, а не набор классов col-lg-6 col-md-6 col-sm-12 на каждом элементе;
 *  - **утилитарных классов** (mb-3, text-center и прочих). Их место
 *    в Tailwind, где их генерирует сборщик; писать их руками — это заводить
 *    вторую систему имён поверх первой;
 *  - **брейкпоинтов на каждый размер.** Один рубеж — 860px. Тема различала
 *    lg/md/sm/xs, и три четверти этих различий на реальном контенте
 *    не отличаются ничем.
 */

/* --- Контейнер и секции --------------------------------------------------- */

.ov-container {
    width: 100%;
    max-width: var(--ov-container);
    margin-inline: auto;
    padding-inline: var(--ov-space-4);
}

/* Вертикальный ритм задаётся секцией, а не отступами внутри неё.
   В теме каждая секция несла свой style="padding-top: 100px" — из-за этого
   ритм ломался при любой перестановке блоков. */
.ov-section {
    padding-block: var(--ov-space-16);
}

.ov-section--tight { padding-block: var(--ov-space-12); }
.ov-section--muted { background: var(--ov-surface-2); }
.ov-section--dark {
    background: var(--ov-dark);
    color: var(--ov-inverse);
}
/* Переопределения для тёмной секции собраны в конце файла: правило
   `.ov-section--dark :is(h2)` весит ровно столько же, сколько `.ov-title h2`
   (класс + элемент), и при равном весе выигрывает то, что ниже. Здесь оно
   стояло выше — и заголовок финального блока оставался тёмным на тёмном.
   Найдено скриншотом: текст был почти не виден. */

/* --- Заголовок секции ----------------------------------------------------- */

.ov-title {
    margin-bottom: var(--ov-space-8);
    max-width: 46rem;
}

.ov-title--center {
    margin-inline: auto;
    text-align: center;
}

.ov-title__eyebrow {
    display: block;
    margin-bottom: var(--ov-space-2);
    color: var(--ov-brand);
    font-family: var(--ov-font-heading);
    font-size: var(--ov-text-sm);
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.ov-title h2 {
    margin: 0;
    font-family: var(--ov-font-heading);
    font-size: var(--ov-text-3xl);
    line-height: var(--ov-leading-tight);
    color: var(--ov-ink);
}

.ov-title p {
    margin: var(--ov-space-4) 0 0;
    font-size: var(--ov-text-lg);
    line-height: var(--ov-leading-normal);
    color: var(--ov-body);
}

/* --- Раскладки ------------------------------------------------------------ */

/* Две колонки. Ширины задаются модификатором, а не классом на каждой колонке. */
.ov-split {
    display: grid;
    gap: var(--ov-space-12);
    align-items: center;
    grid-template-columns: 1fr 1fr;
}

.ov-split--text-first { grid-template-columns: 1.1fr 0.9fr; }

/* Две карточки рядом выравниваются по верху и получают равную высоту.
   Центровка по вертикали верна для пары «текст + иллюстрация», но на паре
   карточек она разводила их верхние кромки: на /about соседние карточки
   начинались с разницей в 26px, и ряд выглядел съехавшим. */
.ov-split > .ov-card { align-self: stretch; }

/* Карточки. auto-fit вместо фиксированного числа колонок: ряд сам решает,
   сколько карточек поместится, и при трёх карточках на планшете не остаётся
   висящей одинокой в третьем ряду. */
.ov-cards {
    display: grid;
    gap: var(--ov-space-6);
    grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
}

.ov-cards--wide {
    grid-template-columns: repeat(auto-fit, minmax(24rem, 1fr));
}

/* Три в ряд при шести карточках: auto-fit по умолчанию раскладывает их
   на 4+2, и вторая строка выглядит обрезанной. */
.ov-cards--3 {
    grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
}

/* --- Карточка ------------------------------------------------------------- */

.ov-card {
    display: flex;
    flex-direction: column;
    gap: var(--ov-space-3);
    padding: var(--ov-space-8);
    background: var(--ov-surface);
    border: 1px solid var(--ov-border);
    border-radius: var(--ov-radius-lg);
    box-shadow: var(--ov-shadow-sm);
}

.ov-card h3 {
    margin: 0;
    font-family: var(--ov-font-heading);
    font-size: var(--ov-text-xl);
    line-height: var(--ov-leading-tight);
    color: var(--ov-ink);
}

.ov-card p { margin: 0; color: var(--ov-body); line-height: var(--ov-leading-normal); }

.ov-card--accent {
    border-color: var(--ov-brand);
    box-shadow: var(--ov-shadow);
}

/* --- Кнопки --------------------------------------------------------------- */

/*
 * min-height 44px — не запас на глаз, а нижняя граница удобной тап-зоны
 * из рекомендаций и Apple, и Google. В теме кнопки и ссылки-действия
 * доходили до 28px по высоте.
 */
.ov-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--ov-space-2);
    min-height: 44px;
    padding: var(--ov-space-3) var(--ov-space-8);
    border: 1px solid transparent;
    border-radius: var(--ov-radius);
    font-family: var(--ov-font-heading);
    font-size: var(--ov-text-base);
    font-weight: 600;
    line-height: 1.2;
    text-align: center;
    text-decoration: none;
    cursor: pointer;
    transition: background-color 160ms ease, color 160ms ease, border-color 160ms ease;
}

.ov-btn--primary {
    background: var(--ov-brand);
    color: var(--ov-inverse);
}
.ov-btn--primary:hover { background: var(--ov-brand-hover); color: var(--ov-inverse); }

.ov-btn--ghost {
    background: transparent;
    border-color: var(--ov-brand);
    color: var(--ov-brand);
}
.ov-btn--ghost:hover { background: var(--ov-brand-soft); }

.ov-section--dark .ov-btn--ghost {
    border-color: var(--ov-inverse);
    color: var(--ov-inverse);
}
.ov-section--dark .ov-btn--ghost:hover {
    background: var(--ov-inverse);
    color: var(--ov-ink);
}

/* --- Текст и списки ------------------------------------------------------- */

.ov-prose { max-width: 44rem; color: var(--ov-body); }
.ov-prose > * + * { margin-top: var(--ov-space-4); }
.ov-prose p { margin: 0; font-size: var(--ov-text-base); line-height: var(--ov-leading-relaxed); }

/* Отступ между соседними абзацами — отдельным правилом с элементом в селекторе.
 *
 * Иначе он не работает вовсе. `.ov-prose > * + *` весит (0,1,0), а сброс
 * `.ov-prose p { margin: 0 }` — (0,1,1), и элемент в селекторе перевешивает
 * независимо от порядка строк. Пока в блоке был один абзац, этого не было
 * видно; на /about три абзаца подряд слиплись в сплошную простыню
 * без единого просвета. */
.ov-prose > p + p,
.ov-prose > p + ul,
.ov-prose > p + ol,
.ov-prose > ul + p,
.ov-prose > ol + p { margin-top: var(--ov-space-4); }
.ov-prose strong { color: var(--ov-ink); }
/* :not(.ov-btn) обязателен: селектор `.ov-prose a` весит больше, чем `.ov-btn--primary`
   (класс+элемент против одного класса), и без исключения кнопка внутри текста
   получала синий текст на синем фоне — проверено скриншотом, подпись исчезала. */
.ov-prose a:not(.ov-btn) { color: var(--ov-brand); text-decoration: underline; }

.ov-prose--lead p { font-size: var(--ov-text-lg); }

/* Список с маркером-галочкой. Псевдоэлемент вместо иконочного шрифта:
   шрифт ради одного глифа — это лишний файл и лишний запрос. */
.ov-list {
    margin: 0;
    padding: 0;
    list-style: none;
}

.ov-list li {
    position: relative;
    padding-left: var(--ov-space-8);
    margin-bottom: var(--ov-space-3);
    line-height: var(--ov-leading-normal);
    color: var(--ov-body);
}

.ov-list li::before {
    content: '';
    position: absolute;
    left: var(--ov-space-2);
    top: 0.45em;
    width: 0.42em;
    height: 0.75em;
    border-right: 2px solid var(--ov-brand);
    border-bottom: 2px solid var(--ov-brand);
    transform: rotate(45deg);
}

/* Список исключений: тот же примитив, другой знак. */
.ov-list--no li::before {
    top: 0.55em;
    left: var(--ov-space-3);
    width: 0.7em;
    height: 0;
    border-right: 0;
    border-bottom: 2px solid var(--ov-muted);
    transform: none;
}

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

.ov-note {
    padding: var(--ov-space-4) var(--ov-space-6);
    border-left: 3px solid var(--ov-brand);
    background: var(--ov-brand-faint);
    border-radius: 0 var(--ov-radius) var(--ov-radius) 0;
    color: var(--ov-body);
    line-height: var(--ov-leading-normal);
}

.ov-price {
    font-family: var(--ov-font-heading);
    font-size: var(--ov-text-3xl);
    font-weight: 700;
    color: var(--ov-ink);
}

.ov-media img { display: block; width: 100%; height: auto; border-radius: var(--ov-radius-lg); }

/* --- Единственный рубеж --------------------------------------------------- */

@media (max-width: 860px) {
    .ov-section { padding-block: var(--ov-space-12); }

    .ov-split,
    .ov-split--text-first {
        grid-template-columns: 1fr;
        gap: var(--ov-space-8);
    }

    .ov-cards,
    .ov-cards--wide {
        grid-template-columns: 1fr;
    }

    .ov-title h2 { font-size: var(--ov-text-2xl); }

    /* Картинка рядом с текстом на узком экране уходит под текст: показывать
       иллюстрацию раньше сути на телефоне незачем. */
    .ov-split .ov-media { order: 2; }
}

/* --- FAQ на нативном <details> -------------------------------------------
 *
 * Заменяет аккордеон темы (.accordion-box + свой JS). Две причины:
 *
 *  - **Поиск по странице.** Свёрнутый ответ в теме прятался display:none —
 *    Ctrl+F его не находил. Содержимое закрытого <details> браузер ищет
 *    и раскрывает сам. Это была отдельная находка аудита.
 *  - **Клавиатура.** <summary> фокусируется, открывается Enter и Space
 *    и объявляется скринридеру как раскрывающийся блок — без единой строки
 *    JS и без ручных aria-expanded, которые надо не забыть переключить.
 */
.ov-faq {
    display: grid;
    gap: var(--ov-space-4);
    grid-template-columns: repeat(auto-fit, minmax(24rem, 1fr));
    align-items: start;
}

.ov-faq details {
    padding: var(--ov-space-4) var(--ov-space-6);
    background: var(--ov-surface);
    border: 1px solid var(--ov-border);
    border-radius: var(--ov-radius);
}

/* Раскрытый вопрос занимает всю ширину строки.
 *
 * В двухколоночной сетке раскрытие оставляло рядом дыру: соседняя карточка
 * сохраняла свою высоту, и под ней зияло до 200px пустоты — читалось как сбой
 * вёрстки. Заодно ответу достаётся вся ширина, а не половина.
 *
 * Длина строки ограничена: ответ — это проза, и 1150px в строке читать нельзя. */
.ov-faq details[open] {
    grid-column: 1 / -1;
    border-color: var(--ov-brand);
}

.ov-faq details[open] .ov-faq__answer { max-width: 68ch; }

.ov-faq summary {
    display: flex;
    align-items: flex-start;
    gap: var(--ov-space-3);
    min-height: 44px;
    padding-block: var(--ov-space-2);
    font-family: var(--ov-font-heading);
    font-size: var(--ov-text-base);
    font-weight: 600;
    line-height: var(--ov-leading-normal);
    color: var(--ov-ink);
    cursor: pointer;
    list-style: none;
}

/* Свой маркер вместо треугольника: браузерный по-разному выглядит в разных
   движках и не даёт указать цвет. */
.ov-faq summary::-webkit-details-marker { display: none; }

.ov-faq summary::after {
    content: '';
    flex: 0 0 auto;
    margin-left: auto;
    margin-top: 0.45em;
    width: 0.5em;
    height: 0.5em;
    border-right: 2px solid var(--ov-brand);
    border-bottom: 2px solid var(--ov-brand);
    transform: rotate(45deg);
    transition: transform 160ms ease;
}

.ov-faq details[open] summary::after { transform: rotate(-135deg); }

.ov-faq .ov-faq__answer {
    margin: var(--ov-space-2) 0 var(--ov-space-2);
    color: var(--ov-body);
    line-height: var(--ov-leading-relaxed);
}

@media (max-width: 860px) {
    .ov-faq { grid-template-columns: 1fr; }
}


/* --- Тёмная секция: переопределения ---------------------------------------
 *
 * Обязаны идти последними в файле — см. комментарий у .ov-section--dark.
 * Веса искусственно не добавляем (ни !important, ни лишних классов):
 * достаточно порядка.
 */

.ov-section--dark .ov-title h2,
.ov-section--dark .ov-card h3 { color: var(--ov-inverse); }

.ov-section--dark .ov-title p,
.ov-section--dark .ov-prose,
.ov-section--dark .ov-prose p,
.ov-section--dark .ov-list li { color: rgba(255, 255, 255, 0.82); }

.ov-section--dark .ov-title__eyebrow { color: rgba(255, 255, 255, 0.6); }

.ov-section--dark .ov-prose strong { color: var(--ov-inverse); }

/* --- Раскладка «содержимое + сайдбар» -------------------------------------
 *
 * Заменяет `.sidebar-page-container` темы с сеткой col-lg-8 / col-lg-4.
 * Пропорция задана здесь один раз, а не классами на каждой странице.
 */
.ov-with-sidebar {
    display: grid;
    gap: var(--ov-space-12);
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
    align-items: start;
}

/* --- Карточка статьи в списке --------------------------------------------- */

.ov-posts {
    display: grid;
    gap: var(--ov-space-8);
}

.ov-post {
    overflow: hidden;
    background: var(--ov-surface);
    border: 1px solid var(--ov-border);
    border-radius: var(--ov-radius-lg);
    box-shadow: var(--ov-shadow-sm);
}

.ov-post__media {
    position: relative;
    margin: 0;
    /* Коробка под обложку 1200×630 резервируется соотношением, а не высотой:
       так при загрузке ничего не прыгает, независимо от размера файла. */
    aspect-ratio: 1200 / 630;
    background: var(--ov-surface-2);
}

.ov-post__media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.ov-post__date {
    position: absolute;
    top: var(--ov-space-4);
    right: var(--ov-space-4);
    display: inline-flex;
    align-items: center;
    gap: var(--ov-space-2);
    padding: var(--ov-space-2) var(--ov-space-3);
    background: var(--ov-surface);
    border-radius: var(--ov-radius-sm);
    font-size: var(--ov-text-xs);
    color: var(--ov-body);
    box-shadow: var(--ov-shadow-sm);
}

/* Дата справа, а не слева: у брендированных обложек в левом верхнем углу
   стоит чип категории, и плашка накрывала бы его. */

.ov-post__body { padding: var(--ov-space-6) var(--ov-space-8) var(--ov-space-8); }

.ov-post__meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--ov-space-4);
    margin: 0 0 var(--ov-space-3);
    padding: 0;
    list-style: none;
    font-size: var(--ov-text-sm);
    color: var(--ov-muted);
}

.ov-post__meta a { color: var(--ov-body); }

.ov-post h3 {
    margin: 0 0 var(--ov-space-3);
    font-family: var(--ov-font-heading);
    font-size: var(--ov-text-2xl);
    line-height: var(--ov-leading-tight);
}

.ov-post h3 a { color: var(--ov-ink); text-decoration: none; }
.ov-post h3 a:hover { color: var(--ov-brand); }

.ov-post__excerpt {
    margin: 0;
    color: var(--ov-body);
    line-height: var(--ov-leading-relaxed);
}

@media (max-width: 860px) {
    /* minmax(0, 1fr), а не 1fr: у колонки грида по умолчанию `min-width: auto`,
       и широкий блок кода внутри статьи растягивал её шире экрана — на телефоне
       страница уезжала вбок на 156px. */
    .ov-with-sidebar { grid-template-columns: minmax(0, 1fr); }
    .ov-post h3 { font-size: var(--ov-text-xl); }
}

/* --- Сайдбар -------------------------------------------------------------- */

.ov-sidebar { display: grid; gap: var(--ov-space-8); }

/* --- Статья --------------------------------------------------------------- */

.ov-article__media {
    position: relative;
    margin: 0 0 var(--ov-space-8);
    aspect-ratio: 1200 / 630;
    border-radius: var(--ov-radius-lg);
    overflow: hidden;
}

.ov-article__media img { display: block; width: 100%; height: 100%; object-fit: cover; }

.ov-article h1 {
    margin: 0 0 var(--ov-space-6);
    font-family: var(--ov-font-heading);
    font-size: var(--ov-text-4xl);
    line-height: var(--ov-leading-tight);
    color: var(--ov-ink);
}

.ov-article__meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--ov-space-4);
    margin: 0 0 var(--ov-space-4);
    padding: 0;
    list-style: none;
    font-size: var(--ov-text-sm);
    color: var(--ov-muted);
}

.ov-tags {
    display: flex;
    flex-wrap: wrap;
    gap: var(--ov-space-2);
    margin: var(--ov-space-8) 0 0;
    padding: 0;
    list-style: none;
}

.ov-tags a {
    display: inline-flex;
    align-items: center;
    min-height: 32px;
    padding: var(--ov-space-1) var(--ov-space-3);
    background: var(--ov-brand-soft);
    border-radius: var(--ov-radius-sm);
    font-size: var(--ov-text-sm);
    color: var(--ov-brand);
    text-decoration: none;
}

.ov-tags a:hover { background: var(--ov-brand); color: var(--ov-inverse); }

/* --- Комментарии ---------------------------------------------------------- */

.ov-comments { display: grid; gap: var(--ov-space-6); }

.ov-comment {
    display: grid;
    grid-template-columns: 56px 1fr;
    gap: var(--ov-space-4);
    padding: var(--ov-space-6);
    background: var(--ov-surface);
    border: 1px solid var(--ov-border);
    border-radius: var(--ov-radius);
}

/* Ответ сдвинут, а не вложен: вложенность на третьем уровне уводит текст
   в колонку шириной в слово. */
.ov-comment--reply { margin-left: var(--ov-space-12); }

.ov-comment__avatar { margin: 0; }
.ov-comment__avatar img { width: 56px; height: 56px; border-radius: 50%; object-fit: cover; }

.ov-comment__head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--ov-space-3);
    margin-bottom: var(--ov-space-2);
}

.ov-comment__name { margin: 0; font-family: var(--ov-font-heading); font-size: var(--ov-text-base); color: var(--ov-ink); }
.ov-comment__time { font-size: var(--ov-text-xs); color: var(--ov-muted); }
.ov-comment__text { margin: 0; color: var(--ov-body); line-height: var(--ov-leading-relaxed); }

.ov-comment__reply-btn {
    margin-left: auto;
    min-height: 32px;
    padding: var(--ov-space-1) var(--ov-space-3);
    background: transparent;
    border: 1px solid var(--ov-border);
    border-radius: var(--ov-radius-sm);
    font-size: var(--ov-text-sm);
    color: var(--ov-brand);
    cursor: pointer;
}

.ov-comment__reply-btn:hover { border-color: var(--ov-brand); background: var(--ov-brand-soft); }

/* --- Форма ---------------------------------------------------------------- */

.ov-form { display: grid; gap: var(--ov-space-4); }

.ov-form__row {
    display: grid;
    gap: var(--ov-space-4);
    grid-template-columns: 1fr 1fr;
}

/* Перечисление типов по одному — способ забыть один из них: поле «Телефон»
   (type="tel") осталось без рамки и выглядело подписью, а не полем ввода.
   Проще исключить то, что оформляется иначе, чем перечислять то, что нет. */
.ov-form :is(input, textarea, select):not([type="checkbox"]):not([type="radio"]):not([type="hidden"]):not([type="submit"]):not([type="button"]) {
    width: 100%;
    min-height: 44px;
    padding: var(--ov-space-3);
    border: 1px solid var(--ov-border);
    border-radius: var(--ov-radius-sm);
    font-family: var(--ov-font-body);
    font-size: var(--ov-text-base);
    color: var(--ov-ink);
    background: var(--ov-surface);
}

.ov-form textarea { min-height: 8rem; resize: vertical; }

.ov-form :is(input, textarea):focus { border-color: var(--ov-brand); }

@media (max-width: 860px) {
    .ov-article h1 { font-size: var(--ov-text-2xl); }
    .ov-comment { grid-template-columns: 40px 1fr; padding: var(--ov-space-4); }
    .ov-comment__avatar img { width: 40px; height: 40px; }
    .ov-comment--reply { margin-left: var(--ov-space-4); }
    .ov-form__row { grid-template-columns: 1fr; }
}

/* --- Точечная правка шапки до её переезда ---------------------------------
 *
 * Выпадающее подменю у крайних правых пунктов открывается влево-вниз от своего
 * пункта и уходит за правый край окна: на 1280px документ становится шире
 * на 81px, и страница получает горизонтальную прокрутку. Воспроизводится
 * на всех страницах, включая непереехавшие, — это дефект темы.
 *
 * Правило живёт здесь, а не в style.css темы, намеренно: свой слой переживёт
 * переезд шапки, а правка в теме была бы выброшена вместе с ней.
 *
 * Прижимаем подменю двух последних пунктов к правому краю их пункта.
 * Двух, а не одного: у последнего пункта («Профиль») подменю шире самого
 * пункта, и одного правила хватало не всегда при смене состава меню.
 */
.main-menu .navigation > li:last-child > ul,
.main-menu .navigation > li:nth-last-child(2) > ul {
    left: auto;
    right: 0;
}

/* --- Подвал --------------------------------------------------------------- */

.ov-footer {
    padding-block: var(--ov-space-16) var(--ov-space-8);
    background: var(--ov-surface);
    border-top: 1px solid var(--ov-border);
}

.ov-footer__cols {
    display: grid;
    gap: var(--ov-space-8);
    /* Четыре колонки на широком, две на планшете, одна на телефоне —
       без единого класса на самих колонках. */
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
}

.ov-footer h3 {
    margin: 0 0 var(--ov-space-4);
    font-family: var(--ov-font-heading);
    font-size: var(--ov-text-base);
    color: var(--ov-ink);
}

.ov-footer p { margin: 0 0 var(--ov-space-3); color: var(--ov-body); line-height: var(--ov-leading-normal); }
.ov-footer a { color: var(--ov-body); }
.ov-footer a:hover { color: var(--ov-brand); }

.ov-footer ul { margin: 0; padding: 0; list-style: none; }

/* Тап-зона 44px у ссылок подвала: в теме они шли строками по 20px,
   и попасть пальцем в нужную было делом удачи. */
.ov-footer li a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
}

.ov-footer__logo img { display: block; width: 180px; height: auto; }

.ov-footer__bottom {
    display: flex;
    flex-wrap: wrap;
    gap: var(--ov-space-4);
    align-items: center;
    justify-content: space-between;
    margin-top: var(--ov-space-12);
    padding-top: var(--ov-space-6);
    border-top: 1px solid var(--ov-border);
    font-size: var(--ov-text-sm);
}

.ov-footer__bottom ul { display: flex; flex-wrap: wrap; gap: var(--ov-space-6); }
.ov-footer__bottom p { margin: 0; }

/* --- Виджеты сайдбара ------------------------------------------------------ */

.ov-widget {
    padding: var(--ov-space-6);
    background: var(--ov-surface);
    border: 1px solid var(--ov-border);
    border-radius: var(--ov-radius-lg);
}

.ov-widget h3 {
    margin: 0 0 var(--ov-space-4);
    font-family: var(--ov-font-heading);
    font-size: var(--ov-text-lg);
    color: var(--ov-ink);
}

.ov-widget ul { margin: 0; padding: 0; list-style: none; }

/* Тап-зона: в теме пункты сайдбара шли строками по 22px. */
.ov-widget li a {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--ov-space-3);
    min-height: 44px;
    color: var(--ov-body);
    text-decoration: none;
}

.ov-widget li a:hover { color: var(--ov-brand); }
.ov-widget li a span { color: var(--ov-muted); font-size: var(--ov-text-sm); }

/* Теги — облако, а не список строк. */
.ov-widget--tags ul { display: flex; flex-wrap: wrap; gap: var(--ov-space-2); }

.ov-widget--tags li a {
    min-height: 36px;
    padding: var(--ov-space-1) var(--ov-space-3);
    background: var(--ov-brand-soft);
    border-radius: var(--ov-radius-sm);
    font-size: var(--ov-text-sm);
    color: var(--ov-brand);
}

.ov-widget--tags li a:hover { background: var(--ov-brand); color: var(--ov-inverse); }

/* Превью статьи: обложка в её собственных пропорциях.
   В теме здесь стоял квадрат 90x90 с object-fit: cover — из брендированной
   обложки 1200x630 вырезался кусок с обрывками заголовка, а чип категории
   (левый верхний угол макета) в вырезку не попадал вовсе. */
.ov-widget__posts { display: grid; gap: var(--ov-space-4); }

.ov-widget__post {
    display: grid;
    grid-template-columns: 120px 1fr;
    gap: var(--ov-space-3);
    align-items: start;
}

.ov-widget__post figure { margin: 0; }

.ov-widget__post img {
    display: block;
    width: 120px;
    height: 63px;
    object-fit: cover;
    border-radius: var(--ov-radius-sm);
}

.ov-widget__post time { display: block; font-size: var(--ov-text-xs); color: var(--ov-muted); }

.ov-widget__post h4 {
    margin: var(--ov-space-1) 0 0;
    font-family: var(--ov-font-heading);
    font-size: var(--ov-text-sm);
    line-height: var(--ov-leading-normal);
}

.ov-widget__post h4 a { color: var(--ov-ink); text-decoration: none; }
.ov-widget__post h4 a:hover { color: var(--ov-brand); }

/* --- Контакты -------------------------------------------------------------- */

/* Список способов связи: маркер-галочка тут ни к чему, у каждого пункта
   своя иконка. */
.ov-contacts li { padding-left: 0; }
.ov-contacts li::before { content: none; }

.ov-contacts li a {
    display: inline-flex;
    align-items: center;
    gap: var(--ov-space-2);
    min-height: 44px;
    color: var(--ov-body);
    text-decoration: none;
}

.ov-contacts li a:hover { color: var(--ov-brand); }

.ov-social {
    display: flex;
    gap: var(--ov-space-3);
    margin: 0;
    padding: 0;
    list-style: none;
}

.ov-social a {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    background: var(--ov-brand-soft);
    border-radius: var(--ov-radius);
    color: var(--ov-brand);
}

.ov-social a:hover { background: var(--ov-brand); color: var(--ov-inverse); }

/* Карточка с несколькими смысловыми блоками: между ними нужен воздух. */
.ov-card > div + div { margin-top: var(--ov-space-6); }

/* --- Узкая колонка под форму ----------------------------------------------
 *
 * Вход, регистрация, профиль. У страницы входа было четыре вложенных
 * контейнера (`sidebar-page-container blog-details` → `auto-container` →
 * `container` → `row justify-content-center` → `col-sm-5`), причём один
 * из них — от страницы статьи. Здесь одна колонка с ограничением по ширине.
 */
.ov-narrow {
    max-width: 32rem;
    margin-inline: auto;
}

.ov-field { display: block; }

.ov-field label {
    display: block;
    margin-bottom: var(--ov-space-2);
    font-size: var(--ov-text-sm);
    font-weight: 600;
    color: var(--ov-ink);
}

.ov-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--ov-space-3);
    align-items: center;
}

.ov-alert {
    padding: var(--ov-space-3) var(--ov-space-4);
    border-radius: var(--ov-radius-sm);
    line-height: var(--ov-leading-normal);
}

.ov-alert--error { background: var(--ov-danger-soft); color: var(--ov-danger); }
.ov-alert--ok { background: var(--ov-success-soft); color: var(--ov-success); }

/* Иконка в карточке. Размер задан здесь, а не в разметке: у иконочного
   шрифта размер это font-size, и повторять его в каждом блоке незачем. */
.ov-card__icon {
    margin: 0 0 var(--ov-space-2);
    font-size: 2.25rem;
    line-height: 1;
    color: var(--ov-brand);
}

/* Заголовок карточки может быть ссылкой — цвет наследуется от заголовка. */
.ov-card h3 a { color: inherit; text-decoration: none; }
.ov-card h3 a:hover { color: var(--ov-brand); }

/* --- Карточка публичного продукта -----------------------------------------
 *
 * Переехало из локального <style> в sub/featured-products.html.twig:
 * там были литеральные цвета (#eceef5, #55606f, #8b5cf6) и свои тени.
 * Здесь всё на токенах, кроме фиолетового акцента продукта — он не входит
 * в палитру сайта и остаётся собственным цветом карточки.
 */
.ov-product { transition: transform 250ms ease, box-shadow 250ms ease; }
.ov-product:hover { transform: translateY(-4px); box-shadow: var(--ov-shadow); }

.ov-product__head {
    display: flex;
    align-items: center;
    gap: var(--ov-space-4);
}

.ov-product__logo { flex: 0 0 auto; width: 56px; height: 56px; }
.ov-product__logo svg { display: block; width: 56px; height: 56px; }

.ov-product h3 { font-size: var(--ov-text-2xl); }

.ov-product__badge {
    display: inline-block;
    margin-top: var(--ov-space-1);
    padding: 2px var(--ov-space-3);
    border-radius: 999px;
    font-size: var(--ov-text-xs);
    font-weight: 600;
    color: #6d28d9;
    background: #f2ecfe;
}

.ov-product__badge--php { color: #1d4ed8; background: var(--ov-brand-soft); }

.ov-product__links {
    display: flex;
    flex-wrap: wrap;
    gap: var(--ov-space-2);
    margin-top: auto;
}

.ov-product__links a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding: var(--ov-space-2) var(--ov-space-4);
    border: 1px solid var(--ov-border);
    border-radius: var(--ov-radius);
    font-size: var(--ov-text-sm);
    font-weight: 600;
    color: var(--ov-body);
    text-decoration: none;
}

.ov-product__links a:hover { background: var(--ov-brand); border-color: var(--ov-brand); color: var(--ov-inverse); }
.ov-product__links a.primary { background: #8b5cf6; border-color: #8b5cf6; color: var(--ov-inverse); }
.ov-product__links a.primary:hover { background: #7c3aed; border-color: #7c3aed; }

/* Роль в карточке проекта — прижата к низу, чтобы у карточек разной высоты
   строки роли выстраивались в один ряд. */
.ov-card__role {
    margin-top: auto;
    padding-top: var(--ov-space-3);
    color: var(--ov-brand);
    font-weight: 600;
}

/* Первый экран главной: имя и строка со сменяющимся словом. */
.ov-title h1 {
    margin: 0;
    font-family: var(--ov-font-heading);
    font-size: var(--ov-text-4xl);
    line-height: var(--ov-leading-tight);
    color: var(--ov-ink);
}

.ov-banner__tagline {
    margin: var(--ov-space-2) 0 0;
    font-family: var(--ov-font-heading);
    font-size: var(--ov-text-2xl);
    font-weight: 700;
    color: var(--ov-brand);
    /* Место под самое длинное слово: без него строка прыгает при смене. */
    min-height: 1.4em;
}

@media (max-width: 860px) {
    .ov-title h1 { font-size: var(--ov-text-3xl); }
    .ov-banner__tagline { font-size: var(--ov-text-xl); }
}

/* --- Совместимость: то, что осталось от Bootstrap в разметке ---------------
 *
 * bootstrap.css (192 КБ) уходит, но два десятка его классов ещё держатся
 * в шаблонах инструментов, баннера cookie и служебных страниц. Переписывать
 * их разом — лишний риск ради косметики; вместо этого здесь определены ровно
 * те правила, на которые они опираются.
 *
 * Это временный слой: по мере правки шаблонов классы заменяются на `ov-*`,
 * и блок сокращается. Когда он опустеет — удалить целиком.
 */

.d-none { display: none !important; }
.clearfix::after { content: ''; display: table; clear: both; }
.pull-left { float: left; }
.pull-right { float: right; }
.text-center { text-align: center; }

/* Доступное имя без визуального текста. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.alert {
    padding: var(--ov-space-3) var(--ov-space-4);
    border-radius: var(--ov-radius-sm);
    line-height: var(--ov-leading-normal);
}

.alert-danger { background: var(--ov-danger-soft); color: var(--ov-danger); }
.alert-success { background: var(--ov-success-soft); color: var(--ov-success); }
.alert-warning { background: #fbf0e2; color: #c9761a; }

.form-group { margin-bottom: var(--ov-space-4); }
.form-group label { display: block; margin-bottom: var(--ov-space-2); font-size: var(--ov-text-sm); font-weight: 600; color: var(--ov-ink); }

.form-control {
    width: 100%;
    min-height: 44px;
    padding: var(--ov-space-3);
    border: 1px solid var(--ov-border);
    border-radius: var(--ov-radius-sm);
    font-family: var(--ov-font-body);
    font-size: var(--ov-text-base);
    color: var(--ov-ink);
    background: var(--ov-surface);
}

.form-control:focus { border-color: var(--ov-brand); }

.form-check { display: flex; align-items: center; gap: var(--ov-space-2); margin-bottom: var(--ov-space-2); }
.form-check-input { width: 18px; height: 18px; }
.form-check-label { margin: 0; }

/* Кнопки Bootstrap ведут себя как наши: те же токены, та же тап-зона. */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--ov-space-2);
    min-height: 44px;
    padding: var(--ov-space-3) var(--ov-space-6);
    border: 1px solid transparent;
    border-radius: var(--ov-radius);
    font-family: var(--ov-font-heading);
    font-size: var(--ov-text-base);
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
    background: var(--ov-brand-soft);
    color: var(--ov-brand);
}

.btn:hover { background: var(--ov-brand); color: var(--ov-inverse); }
.btn-primary { background: var(--ov-brand); color: var(--ov-inverse); }
.btn-primary:hover { background: var(--ov-brand-hover); color: var(--ov-inverse); }
.btn-secondary { background: transparent; border-color: var(--ov-border); color: var(--ov-body); }
.btn-secondary:hover { background: var(--ov-surface-2); color: var(--ov-ink); }
.btn-outline { background: transparent; border-color: var(--ov-border); color: var(--ov-body); }
.btn-outline:hover { background: var(--ov-brand-soft); border-color: var(--ov-brand); color: var(--ov-brand); }
.btn-sm { min-height: 36px; padding: var(--ov-space-2) var(--ov-space-3); font-size: var(--ov-text-sm); }

/* ==========================================================================
   Шапка и мобильное меню — последний компонент, переехавший с темы
   ========================================================================== */

.ov-header {
    position: relative;
    z-index: 100;
    background: var(--ov-surface);
    border-bottom: 1px solid var(--ov-border);
}

.ov-header__inner {
    display: flex;
    align-items: center;
    gap: var(--ov-space-6);
    min-height: 76px;
}

.ov-header__logo { flex: 0 0 auto; }
.ov-header__logo img { display: block; width: 160px; height: auto; }

/* `min-width: 0` — не косметика.
   Флекс-элемент по умолчанию не ужимается уже своего содержимого
   (`min-width: auto`), поэтому правая часть шапки — поиск, кнопка входа,
   бургер — на 1280px вылезала за контейнер на 12px и давала горизонтальную
   прокрутку всему сайту. Ширина ходовая: типичный ноутбук. */
.ov-header__aside {
    display: flex;
    align-items: center;
    gap: var(--ov-space-3);
    margin-left: auto;
    min-width: 0;
}

/* --- Основная навигация ---------------------------------------------------- */

.ov-nav__list {
    display: flex;
    align-items: center;
    gap: var(--ov-space-1);
    margin: 0;
    padding: 0;
    list-style: none;
}

/* Пункт — строка: подпись и каретка рядом, а не одна под другой.
   Без этого каретка переносилась на следующую строку и ломала высоту шапки. */
.ov-nav__item {
    position: relative;
    display: flex;
    align-items: center;
}

.ov-nav__item > a,
.ov-nav__toggle {
    display: inline-flex;
    align-items: center;
    gap: var(--ov-space-1);
    min-height: 44px;
    padding: var(--ov-space-2) var(--ov-space-3);
    border: 0;
    background: transparent;
    font-family: var(--ov-font-heading);
    font-size: var(--ov-text-base);
    font-weight: 600;
    color: var(--ov-ink);
    text-decoration: none;
    cursor: pointer;
    white-space: nowrap;
}

.ov-nav__item > a:hover,
.ov-nav__toggle:hover { color: var(--ov-brand); }

/* Каретка. Своя, а не иконочный шрифт: один глиф не стоит отдельного файла. */
.ov-nav__caret {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 44px;
    padding: 0;
    border: 0;
    background: transparent;
    cursor: pointer;
    vertical-align: middle;
}

.ov-nav__caret span {
    width: 0.4em;
    height: 0.4em;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(45deg) translate(-2px, -2px);
    color: var(--ov-body);
    transition: transform 160ms ease;
}

/* --- Подменю --------------------------------------------------------------- */

.ov-nav__sub {
    position: absolute;
    top: 100%;
    left: 0;
    z-index: 10;
    min-width: 15rem;
    margin: 0;
    padding: var(--ov-space-2);
    list-style: none;
    background: var(--ov-surface);
    border: 1px solid var(--ov-border);
    border-radius: var(--ov-radius);
    box-shadow: var(--ov-shadow);
    opacity: 0;
    visibility: hidden;
    transform: translateY(6px);
    transition: opacity 160ms ease, transform 160ms ease, visibility 160ms;
}

/* Раскрытие без JS: наведение и фокус внутри пункта.
   :focus-within обязателен — без него подменю недостижимо с клавиатуры. */
.ov-nav__item:hover > .ov-nav__sub,
.ov-nav__item:focus-within > .ov-nav__sub,
.ov-nav__item.is-open > .ov-nav__sub {
    opacity: 1;
    visibility: visible;
    transform: none;
}

.ov-nav__sub a {
    display: block;
    min-height: 40px;
    padding: var(--ov-space-2) var(--ov-space-3);
    border-radius: var(--ov-radius-sm);
    font-size: var(--ov-text-sm);
    color: var(--ov-body);
    text-decoration: none;
    line-height: 1.4;
    display: flex;
    align-items: center;
}

.ov-nav__sub a:hover { background: var(--ov-brand-soft); color: var(--ov-brand); }

/* Широкое подменю с группами — ответ на «15 плоских пунктов». */
.ov-nav__sub--wide {
    display: grid;
    grid-template-columns: repeat(2, minmax(13rem, 1fr));
    gap: var(--ov-space-2) var(--ov-space-4);
    padding: var(--ov-space-4);
}

/* Прижимаем к правому краю пункта, если он во второй половине шапки:
   иначе широкое подменю уходит за край окна. */
.ov-nav__item:nth-last-child(-n+3) > .ov-nav__sub { left: auto; right: 0; }

.ov-nav__group ul { margin: 0; padding: 0; list-style: none; }

.ov-nav__group-title {
    margin: 0 0 var(--ov-space-1);
    padding-inline: var(--ov-space-3);
    font-family: var(--ov-font-heading);
    font-size: var(--ov-text-xs);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ov-muted);
}

/* --- Поиск ----------------------------------------------------------------- */

.ov-search { display: flex; align-items: center; min-width: 0; }

/* Поле сжимается от 12rem до 7rem, а не держит ширину любой ценой:
   на узком ноутбуке лишние сантиметры поиска стоили горизонтальной
   прокрутки всей странице. Ниже 7rem поле перестаёт быть полем — там
   уже работает мобильное меню со своей формой поиска. */
.ov-search input {
    flex: 0 1 12rem;
    width: 12rem;
    min-width: 7rem;
    min-height: 44px;
    padding: var(--ov-space-2) var(--ov-space-3);
    border: 1px solid var(--ov-border);
    border-right: 0;
    border-radius: var(--ov-radius) 0 0 var(--ov-radius);
    font-family: var(--ov-font-body);
    font-size: var(--ov-text-sm);
    color: var(--ov-ink);
    background: var(--ov-surface);
}

.ov-search button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    min-height: 44px;
    border: 1px solid var(--ov-border);
    border-radius: 0 var(--ov-radius) var(--ov-radius) 0;
    background: var(--ov-surface-2);
    color: var(--ov-body);
    cursor: pointer;
}

.ov-search button:hover { background: var(--ov-brand); border-color: var(--ov-brand); color: var(--ov-inverse); }
.ov-search button svg { width: 18px; height: 18px; }

/* Подсветка фокуса — на всей связке «поле + кнопка», а не на поле отдельно.
 *
 * Поле и кнопка образуют одну «пилюлю»: у поля скруглены левые углы и снята
 * правая граница, у кнопки скруглены правые. Обводка вокруг одного поля
 * обрезала эту пилюлю пополам — рамка заканчивалась там, где начиналась
 * кнопка, и кнопка торчала за ней отдельным прямоугольником. Замер: поле
 * 990…1134, кнопка 1134…1166, обводка только по 1134.
 *
 * Клавиатурному пользователю по-прежнему видно, на чём именно фокус:
 * у кнопки при фокусе меняется заливка. */
.ov-search:focus-within {
    outline: 2px solid var(--ov-brand);
    outline-offset: 2px;
    border-radius: var(--ov-radius);
}

.ov-search input:focus-visible,
.ov-search button:focus-visible { outline: none; }

.ov-search button:focus-visible {
    background: var(--ov-brand-soft);
    border-color: var(--ov-brand);
    color: var(--ov-brand);
}

/* --- Бургер ---------------------------------------------------------------- */

.ov-burger {
    display: none;
    flex-direction: column;
    justify-content: center;
    gap: 5px;
    width: 44px;
    height: 44px;
    padding: 0 10px;
    border: 1px solid var(--ov-border);
    border-radius: var(--ov-radius-sm);
    background: var(--ov-surface);
    cursor: pointer;
}

.ov-burger span {
    display: block;
    height: 2px;
    background: var(--ov-ink);
    border-radius: 2px;
}

/* --- Мобильное меню -------------------------------------------------------- */

.ov-mobile-menu {
    position: fixed;
    inset: 0;
    z-index: 200;
}

.ov-mobile-menu__backdrop {
    position: absolute;
    inset: 0;
    background: rgba(15, 23, 42, 0.5);
    opacity: 0;
    transition: opacity 300ms ease;
}

.ov-mobile-menu__panel {
    position: absolute;
    top: 0;
    right: 0;
    width: min(320px, 88vw);
    height: 100%;
    padding: var(--ov-space-6);
    /* Своя прокрутка вместо mCustomScrollbar: плагин весил 30 КБ и тянул
       за собой jQuery ради одной панели. */
    overflow-y: auto;
    overscroll-behavior: contain;
    background: var(--ov-surface);
    box-shadow: var(--ov-shadow);
    transform: translateX(100%);
    visibility: hidden;
    transition: transform 300ms ease, visibility 300ms;
}

.ov-menu-open .ov-mobile-menu__backdrop { opacity: 1; }
.ov-menu-open .ov-mobile-menu__panel { transform: none; visibility: visible; }

/* Фон под меню не прокручивается: иначе палец листает страницу, а не меню. */
.ov-menu-open { overflow: hidden; }

.ov-mobile-menu__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--ov-space-4);
    margin-bottom: var(--ov-space-6);
}

.ov-mobile-menu__logo img { display: block; width: 140px; height: auto; }

.ov-mobile-menu__close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border: 1px solid var(--ov-border);
    border-radius: var(--ov-radius-sm);
    background: var(--ov-surface);
    color: var(--ov-body);
    cursor: pointer;
}

.ov-mobile-menu__list,
.ov-mobile-menu__list ul {
    margin: 0 0 var(--ov-space-6);
    padding: 0;
    list-style: none;
}

.ov-mobile-menu__list li { position: relative; }

.ov-mobile-menu__list a,
.ov-mobile-menu__label {
    display: flex;
    align-items: center;
    min-height: 48px;
    padding: var(--ov-space-2) 0;
    font-family: var(--ov-font-heading);
    font-size: var(--ov-text-base);
    font-weight: 600;
    color: var(--ov-ink);
    text-decoration: none;
    border-bottom: 1px solid var(--ov-border);
}

.ov-mobile-menu__list ul {
    margin: 0 0 var(--ov-space-2) var(--ov-space-4);
}

.ov-mobile-menu__list ul a {
    font-weight: 400;
    font-size: var(--ov-text-sm);
    color: var(--ov-body);
}

.ov-mobile-menu__group {
    padding-top: var(--ov-space-3);
    font-size: var(--ov-text-xs);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ov-muted);
}

.ov-mobile-menu__caret {
    position: absolute;
    top: 0;
    right: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 48px;
    height: 48px;
    border: 0;
    background: transparent;
    cursor: pointer;
}

.ov-mobile-menu__caret span {
    width: 0.45em;
    height: 0.45em;
    border-right: 2px solid var(--ov-body);
    border-bottom: 2px solid var(--ov-body);
    transform: rotate(45deg) translate(-2px, -2px);
    transition: transform 160ms ease;
}

.ov-mobile-menu__caret[aria-expanded="true"] span {
    transform: rotate(-135deg) translate(-2px, -2px);
}

.ov-mobile-menu__contacts h4 {
    margin: 0 0 var(--ov-space-3);
    font-family: var(--ov-font-heading);
    font-size: var(--ov-text-base);
    color: var(--ov-ink);
}

.ov-mobile-menu__contacts ul { margin: 0; padding: 0; list-style: none; }

.ov-mobile-menu__contacts a {
    display: flex;
    align-items: center;
    min-height: 44px;
    color: var(--ov-body);
    text-decoration: none;
    font-size: var(--ov-text-sm);
}

.ov-mobile-menu__contacts a:hover { color: var(--ov-brand); }

.ov-mobile-menu .ov-search { margin-bottom: var(--ov-space-6); }
.ov-mobile-menu .ov-search input { width: 100%; }

/* --- Двусторонний конвертер ------------------------------------------------- */

/* Панель = поле + кнопки, которые к нему относятся. Кнопка преобразования
   стоит под своим источником и пишет результат в соседнюю панель, кнопка
   копирования — рядом с ней и копирует своё же поле. */
.ov-convert {
    display: grid;
    gap: var(--ov-space-6);
    grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr));
    align-items: start;
}

.ov-convert__panel {
    display: flex;
    flex-direction: column;
    gap: var(--ov-space-3);
    min-width: 0;
}

.ov-convert__title {
    margin: 0;
    font-size: var(--ov-text-lg);
    font-weight: 600;
    color: var(--ov-ink);
}

.ov-convert__panel textarea {
    width: 100%;
    min-height: 22rem;
    resize: vertical;
    font-family: var(--ov-font-mono);
    font-size: var(--ov-text-sm);
}

.ov-convert__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--ov-space-3);
}

.ov-convert__error {
    margin: var(--ov-space-4) 0 0;
    padding: var(--ov-space-3) var(--ov-space-4);
    border: 1px solid var(--ov-danger);
    border-radius: var(--ov-radius-sm);
    background: var(--ov-danger-soft);
    color: var(--ov-danger);
    font-size: var(--ov-text-sm);
}

/* --- Ряд элементов управления в инструментах -------------------------------- */

/* Подпись-обёртка вокруг поля.
 *
 * `.field-label` из темы задумана как отдельная подпись над полем и несёт
 * `margin-bottom: 6px`. Инструменты используют её иначе — оборачивают в неё
 * само поле: `<label class="field-label">Пароль <input></label>`. Отступ
 * при этом оказывается не между подписью и полем, а под всей связкой,
 * и в ряду с кнопкой она встаёт на 6px выше: поле кончается на 472,
 * кнопка — на 478. Отсюда «кнопка не вровень» на /tools/password-hash
 * и /tools/uuid.
 *
 * Правило переопределяет тему только для обёрток: колонка «подпись → поле»
 * с нормальным зазором и без хвостового отступа. Подписи, стоящие
 * отдельно, работают как раньше. */
.tools-scope label.field-label:has(input, select, textarea) {
    display: flex;
    flex-direction: column;
    gap: var(--ov-space-2);
    margin-bottom: 0;
}

/* --- Факты в карточке ------------------------------------------------------ */

/* Список достижений внутри карточки: заголовок и абзац под ним.
 *
 * До 31.07.2026 здесь была разметка темы (`single-item` + `icon-box`
 * + `flaticon-*`). Заголовок и текст стояли вплотную — между ними не было
 * ни одного пикселя, — а иконка рисовалась шрифтом Roboto вместо Flaticon
 * и выдавала не значок, а пустой глиф из приватной зоны Unicode.
 * Иконки убраны совсем: три строки заголовков читаются лучше без значков,
 * которые ничего не добавляют. */
.ov-facts { display: grid; gap: var(--ov-space-8); }

.ov-facts__item h3 {
    margin: 0 0 var(--ov-space-3);
    font-size: var(--ov-text-lg);
    line-height: var(--ov-leading-tight);
    color: var(--ov-ink);
}

.ov-facts__item p { margin: 0; line-height: var(--ov-leading-relaxed); }
.ov-facts__item .ov-list { margin-top: var(--ov-space-4); }

/* Карточка не прилипает к вводному абзацу над ней: до правки её верхняя
   кромка совпадала с последней строкой текста пиксель в пиксель. */
.ov-card.ov-facts { margin-top: var(--ov-space-6); }

/* --- Постраничная навигация ------------------------------------------------ */

/* Переехала с классов темы (.pagination-weapper / .pagination) 31.07.2026.
   Обёртка стала <nav> с подписью: раньше это был <div>, и для скринридера
   список страниц ничем не отличался от любого другого списка ссылок. */
.ov-pagination { margin-top: var(--ov-space-8); }

.ov-pagination ul {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--ov-space-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

.ov-pagination a {
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
    padding: 0 var(--ov-space-3);
    border: 1px solid var(--ov-border);
    border-radius: var(--ov-radius-sm);
    color: var(--ov-body);
    font-size: var(--ov-text-sm);
    background: var(--ov-surface);
}

.ov-pagination a:hover {
    border-color: var(--ov-brand);
    color: var(--ov-brand);
}

.ov-pagination a.active {
    background: var(--ov-brand);
    border-color: var(--ov-brand);
    color: var(--ov-inverse);
}

/* --- Список с галочками ---------------------------------------------------- */

.ov-list-check {
    margin: var(--ov-space-6) 0;
    padding: 0;
    list-style: none;
}

.ov-list-check li {
    position: relative;
    padding-left: var(--ov-space-8);
    margin-bottom: var(--ov-space-3);
    line-height: var(--ov-leading-normal);
}

/* Галочка нарисована рамкой, а не символом: символ ✓ скринридер читает
   вслух на каждом пункте. */
.ov-list-check li::before {
    content: '';
    position: absolute;
    left: 4px;
    top: 6px;
    width: 6px;
    height: 11px;
    border: solid var(--ov-brand);
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}

/* --- Тело статьи ----------------------------------------------------------- */

/* Типографика статьи. Переехала из инлайнового <style> в blog-details
   31.07.2026: там она дублировала цвет бренда шестнадцатеричным числом
   в двух местах, мимо токенов.

   Маркеры списков возвращены. Сброс в этом слое снимает `list-style`
   со всех списков (он нужен навигации, подвалу, сайдбару), а шаблон статьи
   задавал спискам только отступ. Получались абзацы, сдвинутые вправо
   без всякой причины: маркера нет, а отступ есть. */
.article-body { font-size: 1.02rem; }

.article-body h2 {
    margin-top: 2em;
    margin-bottom: .6em;
    font-size: var(--ov-text-2xl);
    line-height: var(--ov-leading-tight);
    font-weight: 700;
}

.article-body h3 {
    margin-top: 1.5em;
    margin-bottom: .5em;
    font-size: var(--ov-text-xl);
    line-height: 1.35;
    font-weight: 700;
}

.article-body > :first-child { margin-top: 0; }
.article-body p { margin-bottom: 1.1em; line-height: var(--ov-leading-relaxed); }

.article-body ul,
.article-body ol {
    margin: 0 0 1.2em;
    padding-left: 1.4em;
}

.article-body ul { list-style: disc; }
.article-body ol { list-style: decimal; }
.article-body li { margin-bottom: .5em; line-height: 1.7; }
.article-body li::marker { color: var(--ov-muted); }

.article-body blockquote {
    margin: 0 0 1.2em;
    padding: .5em 1.2em;
    border-left: 4px solid var(--ov-brand);
    border-radius: 0 var(--ov-radius) var(--ov-radius) 0;
    background: var(--ov-brand-faint);
    color: var(--ov-body);
}

.article-body a { color: var(--ov-brand); text-decoration: underline; }

/* Длинный код и широкие таблицы прокручиваются внутри своего блока,
   а не растягивают страницу. Без этого статья с ASCII-таблицей уезжала
   вбок на телефоне целиком — вместе с шапкой и подвалом. */
.article-body pre,
.article-body table {
    max-width: 100%;
    overflow-x: auto;
}

.article-body table { display: block; }

.article-body img {
    max-width: 100%;
    height: auto;
    margin: .5em 0 1.2em;
    border-radius: var(--ov-radius);
}

/* --- Шапка страницы -------------------------------------------------------- */

/* Полоса с заголовком и хлебными крошками на всех страницах, кроме главной.
   Заменила `.page-title` темы (31.07.2026).

   Выключка левая, а не по центру: по центру полосу ставила тема, весь
   остальной сайт живёт по левому краю контейнера. Центрированная полоса
   каждый раз ломала вертикальную ось страницы.

   Отступы через clamp: у темы стояли жёсткие 140px сверху и 80px снизу,
   и на телефоне до первого слова приходилось листать пол-экрана. */
.ov-pagehead {
    padding: clamp(28px, 6vw, 56px) 0 clamp(20px, 4vw, 40px);
    background: var(--ov-surface-2);
    border-bottom: 1px solid var(--ov-border);
}

.ov-pagehead h1 {
    margin: 0;
    font-family: var(--ov-font-heading);
    font-size: clamp(1.75rem, 4vw, var(--ov-text-3xl));
    line-height: var(--ov-leading-tight);
    font-weight: 700;
    color: var(--ov-ink);
}

/* Крошки — тихая строка под заголовком. Они контекст, а не подпись:
   размер меньше основного текста, цвет приглушённый. */
.ov-pagehead .bread-crumb {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--ov-space-2);
    margin: var(--ov-space-3) 0 0;
    padding: 0;
    list-style: none;
    font-size: var(--ov-text-sm);
    color: var(--ov-muted);
}

/* На странице статьи заголовок стоит над текстом, и в полосе остаются
   одни крошки — тогда верхний отступ им не нужен. */
.ov-pagehead nav:first-child .bread-crumb { margin-top: 0; }

.ov-pagehead .bread-crumb li { display: flex; align-items: center; gap: var(--ov-space-2); }

/* Разделитель — на псевдоэлементе, а не отдельным символом в разметке:
   скринридер не должен читать «стрелка» между пунктами. */
.ov-pagehead .bread-crumb li + li::before {
    content: '/';
    color: var(--ov-border);
}

.ov-pagehead .bread-crumb a { color: var(--ov-body); }
.ov-pagehead .bread-crumb a:hover { color: var(--ov-brand); }

/* Текущая страница — не ссылка и заметно тише остальных пунктов:
   раньше она выглядела ровно как ссылка, то есть обещала переход,
   которого нет. */
.ov-pagehead .bread-crumb li[aria-current='page'] { color: var(--ov-muted); }

/* --- Рубеж ----------------------------------------------------------------- */

@media (max-width: 1200px) {
    .ov-nav,
    .ov-header .ov-search,
    .ov-header__auth { display: none; }

    .ov-burger { display: flex; }
    .ov-header__inner { min-height: 64px; }
    .ov-header__logo img { width: 140px; }
}

/* --- Уменьшенное движение --------------------------------------------------- */

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

   Длительность 0.01ms, а не `transition: none`: правило с нулём остаётся
   переходом, поэтому не роняет код, который на переходы подписан.

   JS-анимации (reveal.js, typewriter.js) читают тот же запрос сами — CSS
   до них не дотягивается. */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }

    html { scroll-behavior: auto !important; }
}
