/* ==========================================================================
   03-components.css — всё, что живёт внутри каркаса: кнопки, карточки,
   формы, бейджи, алерты, пагинация, дропдауны, сайдбарные карточки, поиск,
   облако тегов, профиль, маскоты, jsTree.
   Четвёртый: уточняет базу (01) и наполняет каркас (02).

   Внутри (в исходном порядке blog.css):
     • .user-dropdown*, .header-search, .theme-toggle-btn (базовый);
     • .post-card*, .sidebar-*, дерево категорий в сайдбаре;
     • .search-*, .list-*, .page-header-center, .tags-cloud-page, .sort-*;
     • .pagination / .page-links;
     • .button* и .btn* (ОДИН компонент под двумя наборами имён), .card*,
       .alert*, .badge*, утилиты (.mb-3, .d-flex, .gap-2, .space-y-8, …),
       .page-item/.page-link, .tab-pane, .form-control;
     • .comments-section и компоненты комментариев;
     • .row / .col-md-6, облако тегов в main-content;
     • .header-nav a (иконки в шапке) и мобильные стили профиля;
     • пиксельные маскоты (кот и дуб) с анимациями;
     • FORMS: .field/.form-group, .auth-*, .btn-flat, .vk-btn, .form-check;
     • тема jsTree для .category-tree;
     • контрастные ховеры .social-link (перекрывают 02-layout.css — см.
       комментарий у самого блока в конце файла).

   ЧТО СТОИТ ЗНАТЬ О ПОРЯДКЕ:
     • .theme-toggle-btn здесь — базовое правило десктопной кнопки. Мобильный
       кластер .theme-toggle-btn.mobile-theme-toggle лежит ВЫШЕ, в
       02-layout.css, и это соответствует исходному порядку blog.css.
     • .header-nav a (блок «Header Nav Icons») объявлен ниже медиазапроса
       .header-nav в 02-layout.css — как и в blog.css. Свойства не
       пересекаются (там font/color, здесь display/gap), но порядок сохранён
       на случай будущих правок.
     • Переменные маскотов (--cat-*, --oak-*) и облака тегов (--tag-*)
       переехали в 00-tokens.css; правила, которые их читают, остались здесь.
   ========================================================================== */

/* ==========================================================================
   КЛАВИАТУРНЫЙ ФОКУС (этап 10.6, WCAG 2.1 SC 2.4.7 «Focus Visible»)

   До этого этапа на 3490 строк CSS приходилось три упоминания
   :focus-visible — то есть обводку получали кнопки (.button/.btn), карточки
   постов и .vk-btn, а ссылки в тексте, пункты навигации, элементы сайдбара,
   вкладки и пагинация полагались на браузерную обводку по умолчанию. Она
   есть, но её толщина и цвет не зависят от темы: тонкая тёмная линия
   Chromium на тёмном фоне сайта почти не видна.

   Базовое правило намеренно объявлено с минимальной специфичностью (0,1,0):
   любое уже существующее правило компонента (.theme-toggle-btn:focus,
   .post-card:focus-visible, .btn-flat:focus-visible — все они гасят outline
   и рисуют кольцо через box-shadow, потому что лежат в контейнерах с
   overflow/скруглениями) перебивает его, и переезд ничего не ломает
   визуально. Мышиный клик обводку НЕ вызывает: :focus-visible, а не :focus.

   Цвет — только var(--ring) (= --primary, зависит от темы):
     светлая тема  #0073B8 на фоне страницы #F6F6F6 — 4.86:1, на карточке 5.25:1
     тёмная тема   #4FA0F5 на фоне страницы #020202 — 7.92:1, на карточке 7.73:1
   При норме 3:1 для нетекстовых индикаторов (SC 1.4.11) запас двукратный.
   ========================================================================== */
:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

/* Skip-link «Перейти к содержимому» (base.html, первый элемент <body>).

   ПОЧЕМУ ВСЁ ОФОРМЛЕНИЕ ВНУТРИ :focus. Прятать ссылку — не наша забота: в
   разметке она несёт .sr-only + .sr-only-focusable, обе объявлены в
   01-base.css. .sr-only держит её вырезанной из потока, а .sr-only-focusable
   в фокусе возвращает поток (position: static). Если бы координаты плашки
   стояли в базовом правиле .skip-link (0,1,0), утилита (0,2,0) перебила бы их
   в момент фокуса и ссылка вылезла бы в начало документа, сдвинув шапку.
   Здесь селектор той же силы (0,2,0) и объявлен позже — выигрывает он.

   Заливка --primary-surface, а не --primary: это плашка, и требование к ней —
   контраст с ТЕКСТОМ поверх, а не с фоном страницы (см. комментарий у токена).
   Замеры: текст --primary-foreground на --primary-surface — 4.77:1 в светлой
   теме и 5.38:1 в тёмной (норма 4.5:1); сама плашка против фона страницы —
   4.67:1 и 3.65:1 (норма 3:1). z-index выше шапки (.header — 50), панели
   .mobile-nav (1000) и гамбургера (1001): иначе ссылка вылезала бы под ними. */
.skip-link:focus {
  position: fixed;
  top: 0.5rem;
  left: 0.5rem;
  z-index: 1100;
  width: auto;
  height: auto;
  padding: 0.625rem 1rem;
  border-radius: var(--radius);
  background-color: var(--primary-surface);
  color: var(--primary-foreground);
  font-weight: 600;
  text-decoration: none;
}

/* <main> — цель skip-link, а не интерактивный элемент: он получает фокус
   программно (tabindex="-1"), чтобы чтение продолжилось с содержимого.
   Обводка вокруг всей страницы в этот момент только дезориентирует, а факт
   перехода скринридер и так озвучивает — он начинает читать регион main.
   Это ЕДИНСТВЕННОЕ снятие обводки без замены во всём этапе, и оно не
   нарушает SC 2.4.7: критерий говорит об элементах, получающих фокус
   с клавиатуры в порядке табуляции, а main из него исключён. */
main[tabindex="-1"]:focus {
  outline: none;
}

/* User Dropdown Styles */
.user-dropdown {
    position: relative;
}

.user-dropdown-toggle {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 1rem;
    background-color: var(--muted);
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    color: var(--foreground);
    font-size: 0.875rem;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.2s ease;
}

/* Раньше здесь стояли var(--muted-hover) и var(--border-hover) — токены,
   которых в проекте нет и не было. Без fallback такое объявление не просто
   игнорируется: значение схлопывается в initial, то есть фон становился
   прозрачным, а рамка — цветом текста. Ховер меню пользователя молча не
   работал. Роли «наведённая плашка» в системе нет отдельного токена и не
   нужно: --secondary для этого и заведён, а рамке нужен интерактивный
   контраст 3:1 — его даёт --border-interactive. */
.user-dropdown-toggle:hover {
    background-color: var(--secondary);
    border-color: var(--border-interactive);
}

.user-avatar {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    /* Аватар-инициал: буква лежит поверх заливки — нужен --primary-surface. */
    background-color: var(--primary-surface);
    color: var(--primary-foreground);
    border-radius: 50%;
    font-size: 0.75rem;
    font-weight: 600;
}

.user-name {
    display: none;
}

@media (min-width: 768px) {
    .user-name {
        display: block;
    }
}

.dropdown-arrow {
    /* 12px — прежний размер инлайн-SVG (viewBox 0 0 12 12, width/height=12).
       Этап 10.8 перевёл стрелку на спрайт с viewBox 0 0 24 24; размер теперь
       задаётся здесь, чтобы сохранить оптику dropdown-меню пользователя. */
    width: 0.75rem;
    height: 0.75rem;
    transition: transform 0.2s ease;
}

.user-dropdown.active .dropdown-arrow {
    transform: rotate(180deg);
}

.user-dropdown-menu {
    position: absolute;
    top: calc(100% + 0.5rem);
    right: 0;
    min-width: 12rem;
    background-color: var(--background);
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
    padding: 0.5rem;
    z-index: 50;
    opacity: 0;
    visibility: hidden;
    transform: translateY(-0.5rem);
    transition: all 0.2s ease;
}

.user-dropdown.active .user-dropdown-menu {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
}

.dropdown-item {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.75rem;
    color: var(--foreground);
    text-decoration: none;
    border-radius: 0.375rem;
    font-size: 0.875rem;
    font-weight: 500;
    transition: background-color 0.2s ease;
}

.dropdown-item:hover {
    background-color: var(--muted);
    color: var(--foreground);
}

.dropdown-item svg {
    flex-shrink: 0;
}
.header-search {
    display: none; /* Hidden on mobile */
}
@media (min-width: 768px) {
    .header-search {
        display: flex;
        align-items: center;
        gap: 0.5rem;
        background-color: var(--muted);
        border-radius: 0.5rem;
        padding: 0.5rem 0.75rem;
        min-width: 200px;
    }
    .header-search input {
        background: transparent;
        border: none;
        /* `outline: none` здесь стоял БЕЗУСЛОВНО (не в :focus) — то есть поле
           теряло обводку и при фокусе с клавиатуры, а своей замены у него не
           было: собственной границы у input нет, её рисует .header-search.
           Снято; кольцо ниже — внутрь (offset отрицательный), потому что
           обёртка со скруглением обрезала бы внешнее. */
        font-size: 0.875rem;
        color: var(--foreground);
        width: 100%;
    }
    .header-search input:focus-visible {
        outline: 2px solid var(--ring);
        outline-offset: -2px;
    }
    .header-search input::placeholder {
        color: var(--muted-foreground);
    }
}

/* Theme Toggle Button */
.theme-toggle-btn {
    background: transparent;
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    padding: 0.5rem;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    color: var(--foreground);
    transition: all 0.2s ease;
    position: relative;
}

.theme-toggle-btn:hover {
    background-color: var(--accent);
    border-color: var(--border);
}

.theme-toggle-btn:focus {
    outline: none;
    /* `ring`/`ring-offset` are Tailwind utilities, not CSS properties —
       emulate the intended focus ring with a box-shadow so keyboard users
       actually get a visible focus indicator. */
    box-shadow: 0 0 0 2px var(--background), 0 0 0 4px var(--ring);
}

.theme-toggle-btn span {
    font-size: 1.125rem;
    line-height: 1;
    transition: transform 0.2s ease;
}

.theme-toggle-btn:hover span {
    transform: scale(1.1);
}

/* Theme icons: show the moon in light mode (click → go dark) and the sun in
   dark mode (click → go light). Both SVGs live in the button; CSS toggles
   visibility based on the data-theme attribute on <html>. This replaces the
   previous emoji (🌙/☀) that were set via JS textContent. */
.theme-icon-wrap {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 0;
}
.theme-icon {
    display: none;
}
/* default (no attribute) == light → show moon */
.theme-icon-moon { display: block; }
.theme-icon-sun { display: none; }
:root[data-theme="dark"] .theme-icon-sun { display: block; }
:root[data-theme="dark"] .theme-icon-moon { display: none; }

/* Responsive theme button */
@media (max-width: 767px) {
    .theme-toggle-btn {
        width: 2rem;
        height: 2rem;
        padding: 0.375rem;
    }
    .theme-toggle-btn span {
        font-size: 1rem;
    }
}

/* Blog Post Card
   Redesigned: tighter type scale (was sized for the old 20px body base),
   a coloured accent bar on the left, and a lift-on-hover with a soft shadow
   for a more clickable, modern feel. The whole card is an <a>. */
.post-card {
  display: block;
  text-decoration: none;
  background-color: var(--card);
  border: 1px solid var(--border);
  padding: 1.25rem 1.5rem;
  border-radius: var(--radius);
  transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
  cursor: pointer;
  font-size: 1rem;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
  /* accent bar grows from the left edge on hover */
  position: relative;
  overflow: hidden;
}
.post-card::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--primary);
  transform: scaleY(0);
  transform-origin: top;
  transition: transform 0.25s ease;
}
.post-card:hover {
  transform: translateY(-3px);
  border-color: var(--primary);
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.12);
}
.post-card:hover::before {
  transform: scaleY(1);
}
.post-card:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--background), 0 0 0 4px var(--ring);
}
.post-card-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-bottom: 0.625rem;
}
.post-card-category {
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 0.25rem 0.625rem;
  border-radius: 9999px;
  background-color: var(--secondary);
  color: var(--secondary-foreground);
}
.post-card-info {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.875rem;
  font-size: 0.8125rem;
  color: var(--muted-foreground);
}
.post-card-info span {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
}
.post-card-info svg {
    flex-shrink: 0;
}
.post-card-title {
  font-size: 1.375rem;
  font-weight: 600;
  color: var(--foreground);
  margin-bottom: 0.5rem;
  transition: color 0.2s ease;
  line-height: 1.3;
}
.post-card:hover .post-card-title {
  color: var(--primary);
}
.post-card-excerpt {
  font-size: 0.9375rem;
  color: var(--muted-foreground);
  line-height: 1.6;
  margin-bottom: 1rem;
  /* clamp long excerpts so card heights stay even */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.post-card-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
}
.post-card-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
}
.post-card-tags span {
  font-size: 0.75rem;
  background-color: var(--muted);
  color: var(--muted-foreground);
  padding: 0.2rem 0.5rem;
  border-radius: 0.375rem;
}
.post-card-readmore {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--primary);
  white-space: nowrap;
  transition: gap 0.2s ease;
}
.post-card:hover .post-card-readmore {
  gap: 0.5rem;
}

/* Sidebar Card */
.sidebar-card {
  background-color: var(--card);
  padding: 1.5rem;
  border-radius: var(--radius);
  margin-bottom: 1.5rem;
}
.sidebar-card-title {
  font-size: 1rem;
  font-weight: 600;
  color: var(--foreground);
  margin-bottom: 1rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.sidebar-card-title .icon {
    color: var(--primary);
}

.sidebar-links {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.sidebar-links a {
  font-size: 0.9375rem;
  color: var(--muted-foreground);
  text-decoration: none;
  transition: color 0.2s ease;
}

.sidebar-links a:hover {
  color: var(--primary);
}

/* Empty-state hint inside a .sidebar-links list (e.g. "no popular posts yet"). */
.sidebar-empty {
  color: var(--muted-foreground);
  font-size: 0.875rem;
}

/* "See all →" link at the bottom of a sidebar card. */
.sidebar-more {
  margin-top: 0.75rem;
}
.sidebar-more a {
  font-size: 0.875rem;
  color: var(--primary);
  text-decoration: none;
  font-weight: 500;
}
.sidebar-more a:hover {
  text-decoration: underline;
}

/* Period filter pills (Неделя / Месяц / Всё время) in the popular sidebar card. */
.sidebar-period-tabs {
  display: flex;
  gap: 0.35rem;
  margin-bottom: 0.75rem;
  flex-wrap: wrap;
}
.sidebar-period-tab {
  font-size: 0.75rem;
  padding: 0.25rem 0.625rem;
  border-radius: 999px;
  text-decoration: none;
  background: var(--muted);
  color: var(--muted-foreground);
  transition: background-color 0.2s ease, color 0.2s ease;
}
.sidebar-period-tab:hover {
  color: var(--foreground);
}
.sidebar-period-tab.active {
  /* Активная «таблетка» — залитая плашка с подписью внутри. */
  background: var(--primary-surface);
  color: var(--primary-foreground);
}

.sidebar-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.sidebar-tags a {
  font-size: 0.875rem;
  background-color: var(--muted);
  color: var(--muted-foreground);
  padding: 0.3125rem 0.75rem;
  border-radius: 9999px;
  text-decoration: none;
  transition: all 0.2s ease;
}

.sidebar-tags a:hover {
  /* На ховере тег заливается целиком, название тега читается поверх заливки. */
  background-color: var(--primary-surface);
  color: var(--primary-foreground);
}

.category-item-wrapper {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

/* Scoped styles for show_categories template tag */
.sidebar-links ul {
  list-style: none;
  padding-left: 0;
  margin: 0;
}

/* Стили для дерева категорий - используем стандартный Bootstrap collapse */
.sidebar-links ul.nav {
  list-style: none;
  padding-left: 0;
  margin: 0;
}

.sidebar-links ul.children {
  padding-left: 1.5rem; /* Indent nested lists */
  list-style: none;
  margin: 0;
  display: none; /* По умолчанию скрыты */
}

/* Ветка текущей рубрики раскрыта: тег show_categories вешает на неё класс .in
   и рисует на триггере «минус», то есть разметка всегда утверждала, что
   поддерево открыто, — а правила под .in не было, и оно оставалось скрытым.
   Пока их не согласовать, aria-expanded не может быть правдивым: либо он
   врёт про раскрытость, либо противоречит иконке. Раскрываем — это и есть
   исходный замысел тега (open_slugs — путь до текущей рубрики). */
.sidebar-links ul.children.in {
  display: block;
}

.sidebar-links li {
  margin: 0;
  padding: 0;
}

.sidebar-links li a {
  display: inline-block;
  color: var(--muted-foreground);
  text-decoration: none;
  padding: 0.25rem 0;
  font-size: 0.9375rem;
  transition: color 0.2s ease;
}

.sidebar-links li a:hover {
  color: var(--primary);
  text-decoration: none;
}

.sidebar-links .category-item-wrapper {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 0.25rem;
}

/* Триггер раскрытия поддерева рубрик. С этапа 10.6 это <button>, а не <a>
   без href (тот вообще не попадал в порядок табуляции), поэтому здесь же
   гасим кнопочные стили браузера: снаружи элемент должен выглядеть ровно как
   прежний значок. Размеры 20x20 оставлены как были. */
.sidebar-links .sign {
  margin-left: 0.5rem;
  color: var(--muted-foreground);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  cursor: pointer;
  transition: color 0.2s ease;
  /* Сброс UA-оформления кнопки. */
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  /* Позиционируем .sr-only-подпись относительно самой кнопки: без этого
     absolute-подпись цеплялась бы за далёкого позиционированного предка. */
  position: relative;
}

.sidebar-links .sign:hover {
  color: var(--primary);
  text-decoration: none;
}

.sidebar-links .sign svg {
  color: inherit;
  transition: color 0.2s ease;
}

/* Размер значка плюс/минус. С этапа 10.8 он приходит из спрайта, а .icon
   по умолчанию равен 1em — от `font: inherit` кнопки это дало бы размер
   текста сайдбара. Прежние 12px были жёстко прописаны в атрибутах SVG,
   здесь они и остаются, но уже как стиль, а не как разметка. */
.sidebar-links .sign .icon {
  width: 0.75rem;
  height: 0.75rem;
}

.sidebar-links .lbl {
  flex: 1;
}
/* Search Form */
.search-form {
    margin-bottom: 1rem;
}

.search-input-wrapper {
    display: flex;
    /* Обёртка рисует границу за сам <input>, поэтому контраст как у поля. */
    border: 1px solid var(--border-interactive);
    border-radius: 0.5rem;
    overflow: hidden;
    background-color: var(--background);
}

.search-input {
    flex: 1;
    /* min-width: 0 обязателен, а не «на всякий случай». У флекс-элемента
       min-width по умолчанию auto, и для <input> это его СОБСТВЕННАЯ ширина
       (~20 символов, около 200px) — поле отказывалось сжиматься уже. В колонке
       сайдбара (одна дорожка из четырёх, ~200px на ширине окна 1024px) поле
       занимало её целиком и выдавливало кнопку за правый край обёртки, а
       overflow: hidden у .search-input-wrapper эту кнопку обрезал: на десктопе
       от неё оставалась узкая синяя полоска с половиной лупы. Тот же
       инвариант, что у .main-grid > * в 02-layout.css. */
    min-width: 0;
    padding: 0.75rem;
    border: none;
    /* Здесь `outline: none` тоже стоял безусловно: поиск в сайдбаре и на
       /search/ был единственным полем страницы вообще без индикатора фокуса —
       браузерную обводку сняли, а взамен не дали ничего. Кольцо рисуем
       внутрь: .search-input-wrapper объявлен с overflow: hidden, внешняя
       обводка (offset > 0) была бы им обрезана и не появилась бы. */
    background: transparent;
    color: var(--foreground);
    font-size: 1.09375rem;
}

.search-input:focus-visible {
    outline: 2px solid var(--ring);
    outline-offset: -2px;
}

.search-input::placeholder {
    color: var(--muted-foreground);
}

.search-button {
    /* Залитая кнопка с иконкой лупы поверх — поверхность, не акцент. */
    background-color: var(--primary-surface);
    color: var(--primary-foreground);
    border: none;
    padding: 0.75rem;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background-color 0.2s ease;
    min-width: 2.5rem;
    /* Парная страховка к min-width: 0 у поля: min-width задаёт нижнюю границу,
       но не запрещает флексу отбирать у кнопки ширину сверх неё. Без
       flex-shrink: 0 в узкой колонке ужимался бы padding кнопки. */
    flex-shrink: 0;
}

.search-button:hover {
    /* Ховер — затемнение той же поверхности, а не альфа: с альфой кнопка
       подмешивала бы под себя страницу и в светлой теме ОСВЕТЛЯЛАСЬ, роняя
       контраст иконки поверх (подробности у токена). */
    background-color: var(--primary-surface-hover);
}

.search-links {
    font-size: 0.9375rem;
    text-align: center;
    margin-top: 0.75rem;
    color: var(--muted-foreground);
}

.search-links a {
    color: var(--primary);
    text-decoration: none;
    transition: color 0.2s ease;
}

.search-links a:hover {
    color: var(--foreground);
    text-decoration: underline;
}

/* Sort Controls */
/* List page header: section title + sort control row. */
.list-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: 1rem;
}
.list-title {
    font-size: 1.5rem;
    font-weight: 700;
    color: var(--foreground);
    margin: 0;
}
.list-subtitle {
    color: var(--muted-foreground);
    margin: 0 0 1rem;
    font-size: 0.9375rem;
}

/* Centred header used on the categories / tags index pages. */
.page-header-center {
    margin-bottom: 2rem;
    text-align: center;
}
.page-header-center h2 {
    margin: 0 0 0.375rem;
}
.page-header-center p {
    color: var(--muted-foreground);
    margin: 0;
    font-size: 0.9375rem;
}

/* Category tree (jsTree) card. */
.category-tree {
    max-width: 800px;
    margin: 0 auto;
    padding: 1.5rem;
    background-color: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

/* Tags index page: a wrapping cloud of pill links with post counts. */
.tags-cloud-page {
    display: flex;
    flex-wrap: wrap;
    gap: 0.625rem;
    max-width: 900px;
    margin: 0 auto;
}
.tags-cloud-page .sidebar-tags-tag {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.4375rem 0.875rem;
    font-size: 0.9375rem;
    background-color: var(--muted);
    color: var(--muted-foreground);
    border-radius: 9999px;
    text-decoration: none;
    transition: background-color 0.2s ease, color 0.2s ease;
}
.tags-cloud-page .sidebar-tags-tag:hover {
    /* Заливка тега с названием и счётчиком поверх неё. */
    background-color: var(--primary-surface);
    color: var(--primary-foreground);
}
.tags-cloud-page .tag-count {
    font-size: 0.75rem;
    font-weight: 600;
    background-color: oklch(var(--primary-raw) / 0.15);
    /* Текст здесь --foreground, а не --primary. Причина: подложка счётчика —
       15%-й тинт того же самого синего поверх --muted, то есть цвет текста и
       цвет фона родственные, и в светлой теме они дают всего 3.72:1 при норме
       4.5:1. Понижение светлоты --primary не спасает (даже при 0.48 выходит
       лишь 4.32:1), а усиление тинта делает ХУЖЕ: тинт темнеет навстречу
       тексту и разрыв сокращается ещё сильнее. Нейтральный --foreground даёт
       14.58:1 в светлой и 14.00:1 в тёмной, а «акцентность» счётчика по-
       прежнему читается по синей плашке под ним. */
    color: var(--foreground);
    padding: 0.0625rem 0.4375rem;
    border-radius: 9999px;
}
.tags-cloud-page .sidebar-tags-tag:hover .tag-count {
    /* Подложка — непрозрачный --primary-surface-hover, а не 25% светлого
       поверх заливки. Прежний тинт давал 3.13:1 (светлая) и 3.40:1 (тёмная)
       при норме 4.5:1: шрифт 12px/600, послабление для крупного не применяется.
       Смена ТОЛЬКО цвета текста на --foreground эту пару не чинит — в светлой
       теме выходит 6.21:1, а в тёмной по-прежнему 3.11:1, потому что там
       --foreground сам светлый и композит остаётся середины яркости.
       Затемнение подложки работает в обеих: 6.63:1 и 7.19:1, и счётчик
       вдобавок остаётся темнее самой кнопки, то есть визуально выделен. */
    background-color: var(--primary-surface-hover);
    color: var(--primary-foreground);
}

.sort-controls {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.sort-label {
    font-size: 0.875rem;
    color: var(--muted-foreground);
}

.sort-select {
    background-color: var(--muted);
    color: var(--foreground);
    border: 1px solid var(--border-interactive);
    border-radius: 0.5rem;
    padding: 0.4375rem 1rem;
    font-size: 0.875rem;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.2s ease;
    appearance: none;
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
    background-position: right 0.5rem center;
    background-repeat: no-repeat;
    background-size: 1.25em 1.25em;
    padding-right: 2.25rem;
}

.sort-select:hover {
    border-color: var(--primary);
}

.sort-select:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 2px oklch(var(--primary-raw) / 0.2);
}

/* Post card grid on list pages. Single column on phones, two-up on wide
   screens so the cards breathe instead of stretching full content width. */
.post-card-grid {
    display: grid;
    gap: 1.25rem;
    grid-template-columns: 1fr;
}
@media (min-width: 1024px) {
    .post-card-grid {
        grid-template-columns: repeat(2, 1fr);
        gap: 1.5rem;
    }
}

/* Pagination */
.pagination {
    margin-top: 2rem;
    padding: 1rem 0;
    border-top: 1px solid var(--border);
}

.page-links {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 1rem;
    font-size: 0.875rem;
}

.page-links a {
    color: var(--primary);
    text-decoration: none;
    padding: 0.5rem 1rem;
    border-radius: 0.375rem;
    border: 1px solid var(--border);
    transition: all 0.2s ease;
}

.page-links a:hover {
    background-color: var(--accent);
    border-color: var(--primary);
}

.page-current {
    color: var(--foreground);
    font-weight: 500;
}

/* ==========================================================================
   КНОПКИ
   Один компонент на два набора имён. `.button`/`.button-*` — наши собственные,
   `.btn`/`.btn-*` — бутстраповские, оставшиеся в 44 местах разметки. Раньше это
   были ДВА параллельных набора правил: `.button` без рамки и с радиусом
   0.375rem, `.btn` с прозрачной рамкой и радиусом --radius. Теперь имена
   собраны в общие селекторы — одно правило, несколько псевдонимов. Так
   исключён самый дешёвый способ разъехаться: поправить один набор и забыть про
   второй.

   ПОЧЕМУ ЭТО ПРИШЛОСЬ ПЕРЕПИСАТЬ. Классы .btn-outline-primary,
   .btn-outline-success и .btn-success не были объявлены НИГДЕ: вариантов
   -outline-* в Bootstrap 3.4.1 не существует (они появились в BS4), а
   бутстраповскую заливку .btn-success добивало наше же правило
   `.btn { background-color: transparent; border: 1px solid transparent }`,
   объявленное позже. В результате кнопка «Проверить» в каждом задании главы,
   обе кнопки «Прочитано»/«Отметить прочитанным» и .tut-diff-toggle рендерились
   прозрачным текстом без рамки. Дефект того же рода, что и фантомный токен:
   имя, которого нет, молча ничего не делает и никак себя не проявляет.

   ПРАВИЛО РОЛЕЙ ЦВЕТА (действует во всём файле):
     • заливка — *-surface (--primary-surface, --success-surface, …);
     • ховер заливки — непрозрачное затемнение *-surface-hover, НЕ альфа:
       альфа подмешивает подложку и в светлой теме ОСВЕТЛЯЕТ кнопку, роняя
       текст поверх ниже AA (см. --primary-surface-hover-raw в 00-tokens.css);
     • тот же цвет в роли ТЕКСТА — отдельный токен *-text: у текста и у
       подложки требования направлены в разные стороны.
   ========================================================================== */

.button,
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Кнопки часто содержат <i class="fas …"> перед подписью; gap избавляет
       разметку от &nbsp; и ручных margin у иконок. */
    gap: 0.5rem;
    font-weight: 500;
    line-height: 1.5;
    color: var(--foreground);
    text-align: center;
    text-decoration: none;
    vertical-align: middle;
    cursor: pointer;
    user-select: none;
    background-color: transparent;
    /* Рамка объявлена всегда (прозрачной у залитых вариантов): иначе
       outline-кнопка была бы на 2px крупнее соседней залитой и они не
       выстраивались бы в ряд. */
    border: 1px solid transparent;
    padding: 0.625rem 1.25rem;
    font-size: 1.09375rem;
    /* 0.5rem — тот же радиус, что у .form-control в секции FORMS ниже.
       Кнопка и поле ввода стоят в одной строке (форма задания в главе курса),
       и разные радиусы там сразу читаются как рассогласование. */
    border-radius: 0.5rem;
    transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out,
                color 0.15s ease-in-out;
}

/* Клавиатурный фокус. Именно :focus-visible, а не :focus: обводка нужна тому,
   кто ходит табом, и не нужна после клика мышью. */
.button:focus-visible,
.btn:focus-visible {
    outline: 2px solid var(--ring);
    outline-offset: 2px;
}

.button:active,
.btn:active {
    transform: translateY(1px);
}

/* [disabled] ловит и <button disabled>, и aria-disabled в разметке HTMX;
   .is-disabled — для ссылок, где атрибута disabled не бывает. */
.button[disabled],
.btn[disabled],
.button.is-disabled,
.btn.is-disabled {
    opacity: 0.55;
    cursor: not-allowed;
    /* Сброс active-сдвига: «нажать» выключенную кнопку нельзя. */
    transform: none;
}

/* --- Вариант: первичный (залитый) --------------------------------------- */
/* Заливка и рамка — одна и та же плоскость под текстом, поэтому обе на
   --primary-surface: рамка на --primary дала бы вокруг кнопки светлый ободок
   в тёмной теме. */
.button--primary,
.button-primary,
.btn-primary {
    color: var(--primary-foreground);
    background-color: var(--primary-surface);
    border-color: var(--primary-surface);
}
.button--primary:hover,
.button-primary:hover,
.btn-primary:hover {
    color: var(--primary-foreground);
    background-color: var(--primary-surface-hover);
    border-color: var(--primary-surface-hover);
}

/* --- Вариант: нейтральный контурный ------------------------------------- */
.button--outline,
.button-outline,
.btn-outline {
    background-color: transparent;
    /* Граница — единственное, что очерчивает кнопку, поэтому контрастный
       --border-interactive (3.94:1 / 3.37:1 к карточке), а не декоративный
       --border (1.35:1 — норма для линии-украшения, но не для контура
       интерактивного элемента, SC 1.4.11 требует 3:1). */
    border-color: var(--border-interactive);
    color: var(--foreground);
}
.button--outline:hover,
.button-outline:hover,
.btn-outline:hover {
    background-color: var(--accent);
}

/* --- Вариант: вторичный / «по умолчанию» -------------------------------- */
/* .btn-default — бутстраповское имя из accounts/*, .btn-secondary — из
   комментариев. Роль одна: неакцентное действие рядом с первичным. Рамка
   НЕ на --secondary: та даёт к карточке 1.15:1, то есть кнопку не очерчивает. */
.button--secondary,
.btn-secondary,
.btn-default {
    color: var(--secondary-foreground);
    background-color: var(--secondary);
    border-color: var(--border-interactive);
}
.button--secondary:hover,
.btn-secondary:hover,
.btn-default:hover {
    color: var(--secondary-foreground);
    background-color: var(--accent);
}

.btn-outline-secondary {
    color: var(--muted-foreground);
    /* Было --border (1.35:1) — контур не читался. */
    border-color: var(--border-interactive);
    background-color: transparent;
}
.btn-outline-secondary:hover {
    color: var(--foreground);
    background-color: var(--accent);
}

/* --- Вариант: опасное действие ------------------------------------------ */
.button--danger,
.btn-danger {
    /* Чистый белый, а не --destructive-foreground: тот слегка голубой и на
       красном даёт 4.53:1 против 4.77:1 у белого — запас над AA вдвое тоньше
       без единой выгоды. */
    color: #fff;
    background-color: var(--destructive);
    border-color: var(--destructive);
}
.button--danger:hover,
.btn-danger:hover {
    color: #fff;
    /* Было `oklch(var(--destructive-raw) / 0.9)`: в светлой теме альфа
       подмешивала белую страницу и текст падал с 4.77:1 до 3.54:1 —
       наведение курсора выводило кнопку из AA. Непрозрачный токен: 5.98:1. */
    background-color: var(--destructive-hover);
    border-color: var(--destructive-hover);
}

.btn-outline-danger {
    /* Здесь красный работает как ЦВЕТ ТЕКСТА на прозрачном фоне, а не как
       заливка, поэтому --destructive-text: --destructive в этой роли не дотягивает
       до 4.5:1 на фоне страницы. Рамка остаётся на --destructive:
       для линий норма 3:1, и она с запасом проходит. */
    color: var(--destructive-text);
    border-color: var(--destructive);
    background-color: transparent;
}
.btn-outline-danger:hover {
    color: #fff;
    background-color: var(--destructive);
    border-color: var(--destructive);
}

/* --- Вариант: успех ------------------------------------------------------ */
/* Кнопка «Прочитано» в главе курса (chapter_detail.html) — до этой правки
   не существовало ни в наших модулях, ни в Bootstrap 3. */
.button--success,
.btn-success {
    color: var(--success-foreground);
    background-color: var(--success-surface);
    border-color: var(--success-surface);
}
.button--success:hover,
.btn-success:hover {
    color: var(--success-foreground);
    background-color: var(--success-surface-hover);
    border-color: var(--success-surface-hover);
}

/* Кнопка «Отметить прочитанным» — тоже была невидимой. Текст на --success-text
   (роль текста), рамка на --success-surface (роль линии, норма 3:1). */
.button--outline-success,
.btn-outline-success {
    color: var(--success-text);
    border-color: var(--success-surface);
    background-color: transparent;
}
.button--outline-success:hover,
.btn-outline-success:hover {
    color: var(--success-foreground);
    background-color: var(--success-surface);
    border-color: var(--success-surface);
}

/* --- Вариант: первичный контурный --------------------------------------- */
/* Кнопка «Проверить» в КАЖДОМ задании главы и «Ответить» в комментариях —
   до этой правки прозрачный текст без рамки. */
.button--outline-primary,
.btn-outline-primary {
    color: var(--primary);
    border-color: var(--primary);
    background-color: transparent;
}
.button--outline-primary:hover,
.btn-outline-primary:hover {
    color: var(--primary-foreground);
    background-color: var(--primary-surface);
    border-color: var(--primary-surface);
}

/* --- Вариант: предупреждение -------------------------------------------- */
/* Кнопка «В избранное» на странице статьи. Хардкод #f39c12 снят: он не
   реагировал на тему и в тёмной давал 1.90:1 с --foreground поверх.
   Про тёмные чернила и про рамку на --warning-text — см. 00-tokens.css. */
.button--warning,
.btn-warning {
    color: var(--warning-foreground);
    background-color: var(--warning-surface);
    border-color: var(--warning-text);
}
.button--warning:hover,
.btn-warning:hover {
    color: var(--warning-foreground);
    background-color: var(--warning-surface-hover);
    border-color: var(--warning-text);
}

/* ОСТАВШАЯСЯ ЗАВИСИМОСТЬ. Единственное место, где стоит .btn-outline-warning, —
   кнопка «В избранное» внутри .post-actions (pages/blog.css). Плашка
   .post-actions залита ХАРДКОДНЫМ светлым градиентом #f8f9fa→#e9ecef и не
   следует теме; на ней тёмнотемный --warning-text (светлый янтарь) даёт
   1.94:1. Из этих двух файлов дефект неустраним в принципе: любой корректный
   тематический токен провалится на подложке, которая тему игнорирует. В
   светлой теме правка уже сработала — 2.08:1 → 4.88:1. Чинить надо плашку:
   она помечена «Кандидат №1 на токенизацию» в pages/blog.css. */
.button--outline-warning,
.btn-outline-warning {
    color: var(--warning-text);
    border-color: var(--warning-text);
    background-color: transparent;
}
.button--outline-warning:hover,
.btn-outline-warning:hover {
    color: var(--warning-foreground);
    background-color: var(--warning-surface);
    border-color: var(--warning-text);
}

/* --- Вариант: инверсный контурный --------------------------------------- */
/* ТОЛЬКО для цветных/тёмных подложек: сейчас это hero на странице курсов
   (.series-hero, градиент из --primary-surface). Белый там уместен, потому что
   подложка гарантированно тёмная в ОБЕИХ темах; на обычном фоне страницы этот
   вариант применять нельзя — в светлой теме он станет невидимым. */
.button--light,
.btn-outline-light {
    color: #fff;
    /* 3.85:1 к светлому стопу градиента — контур читается (норма 3:1). */
    border-color: rgba(255, 255, 255, 0.7);
    background-color: transparent;
}
.button--light:hover,
.btn-outline-light:hover {
    color: #fff;
    background-color: rgba(255, 255, 255, 0.18);
    border-color: #fff;
}

/* --- Размеры и раскладка ------------------------------------------------- */
/* Было `font-size: 1.2rem` — «маленькая» кнопка выходила КРУПНЕЕ обычной
   (1.09375rem). Опечатка, а не решение: 0.9375rem восстанавливает смысл. */
.button--sm,
.btn-sm {
    padding: 0.375rem 0.75rem;
    font-size: 0.9375rem;
    border-radius: 0.375rem;
    gap: 0.375rem;
}

/* Кнопка во всю ширину контейнера (accounts/dashboard.html). Класс жил в
   разметке, но не был объявлен нигде, кроме Bootstrap. */
.button--block,
.btn-block {
    display: flex;
    width: 100%;
}
.button--block + .button--block,
.btn-block + .btn-block {
    margin-top: 0.5rem;
}

/* --- Неактивная кнопка ---------------------------------------------------- */
/* `.disabled` как модификатор кнопки объявлял ТОЛЬКО bootstrap.min.css
   (`.btn.disabled{opacity:.65;cursor:not-allowed;box-shadow:none}`). У нас был
   лишь частный случай `.heart-button.disabled` в pages/blog.css, поэтому после
   снятия Bootstrap кнопка «В избранное» для анонима (post_detail.html:89 —
   `btn btn-outline-secondary btn-sm disabled`) выглядела бы полностью рабочей
   и кликабельной, хотя ведёт лишь к подсказке «Войдите».
   `[disabled]` и `fieldset[disabled]` добавлены сюда же: настоящие
   <button disabled> в формах гасил тот же самый набор правил Bootstrap. */
.button.disabled,
.button[disabled],
.btn.disabled,
.btn[disabled],
fieldset[disabled] .btn {
    opacity: 0.65;
    cursor: not-allowed;
    box-shadow: none;
    /* Клик по <a class="btn disabled"> Bootstrap не глушил, и мы тоже не
       глушим: на .heart-button/.btn-sm висит title-подсказка, а
       pointer-events:none убил бы и её показ по наведению. */
}

/* ==========================================================================
   КАРТОЧКА
   `.card` — бутстраповское имя, оставшееся в разметке; `.card--*` — наши
   модификаторы. Общие селекторы, чтобы не заводить второй параллельный набор.
   ========================================================================== */
.card {
    background-color: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

/* Карточка без тени — для вложенных случаев (карточка внутри карточки),
   где двойная тень выглядит грязью. */
.card--flat {
    box-shadow: none;
}

.card-body,
.card__body {
    padding: 1rem;
}

.card-title,
.card__title {
    margin-bottom: 0.5rem;
    font-size: 1.125rem;
    font-weight: 600;
    color: var(--foreground);
}

.card-text,
.card__text {
    color: var(--muted-foreground);
}

/* ==========================================================================
   СООБЩЕНИЯ (.alert)
   Четыре смысловых варианта. Все — полупрозрачный тинт своего цвета плюс
   рамка того же цвета погуще, текст всегда --foreground. Именно здесь альфа
   уместна и безопасна, в отличие от заливки кнопки: текст берётся не с тинта,
   а с --foreground, который по определению контрастен подложке страницы, и
   разбавление тинта эту разницу только увеличивает. Замеры (текст на тинте):
   светлая тема 16.9–18.8:1, тёмная 7.8–12.6:1.
   ========================================================================== */
.alert {
    padding: 0.75rem 1rem;
    margin-bottom: 1rem;
    border: 1px solid transparent;
    border-radius: var(--radius);
    color: var(--foreground);
    font-size: 0.9375rem;
}

.alert-info,
.alert--info {
    background-color: oklch(var(--primary-raw) / 0.1);
    border-color: oklch(var(--primary-raw) / 0.25);
}

.alert-success,
.alert--success {
    background-color: oklch(var(--success-text-raw) / 0.12);
    border-color: oklch(var(--success-text-raw) / 0.3);
}

.alert-warning,
.alert--warning {
    background-color: oklch(var(--warning-text-raw) / 0.14);
    border-color: oklch(var(--warning-text-raw) / 0.3);
}

/* Шапка ошибок формы в accounts/login, register, profile_edit,
   profile_settings. Класс стоял в разметке, но объявлен был только в
   Bootstrap 3 — после снятия бутстрапа блок ошибок стал бы неотличим от
   обычного текста. */
.alert-danger,
.alert--danger {
    background-color: oklch(var(--destructive-raw) / 0.12);
    border-color: oklch(var(--destructive-raw) / 0.35);
}

/* ==========================================================================
   БЕЙДЖ (.badge)
   Базовый вариант теперь НЕЙТРАЛЬНЫЙ, а не «белый текст без фона»: раньше
   `.badge` задавал только `color: white`, и голый `<span class="badge">` был
   белым по белому. Существующая разметка (`badge bg-secondary`,
   `badge bg-light text-dark`) не затронута — утилиты .bg-* и .text-* объявлены
   с !important и перебивают базу.
   ========================================================================== */
.badge {
    display: inline-block;
    padding: 0.25em 0.5em;
    font-size: 0.75em;
    font-weight: 500;
    line-height: 1;
    color: var(--secondary-foreground);
    background-color: var(--secondary);
    text-align: center;
    white-space: nowrap;
    vertical-align: baseline;
    border-radius: 0.375rem;
}

.badge--primary,
.badge-primary {
    color: var(--primary-foreground);
    background-color: var(--primary-surface);
}

.badge--success,
.badge-success {
    color: var(--success-foreground);
    background-color: var(--success-surface);
}

.badge--warning,
.badge-warning {
    color: var(--warning-foreground);
    background-color: var(--warning-surface);
}

.badge--danger,
.badge-danger {
    color: #fff;
    background-color: var(--destructive);
}

.bg-secondary {
    background-color: var(--secondary) !important;
    color: var(--secondary-foreground) !important;
}

.bg-light {
    background-color: var(--muted) !important;
    color: var(--foreground) !important;
}

.text-muted {
    color: var(--muted-foreground) !important;
}

.text-dark {
    color: var(--foreground) !important;
}

/* --------------------------------------------------------------------------
   Утилиты отступов и раскладки. Здесь ТОЛЬКО те, что реально встречаются в
   разметке (список снят грепом по шаблонам), — полноценная утилитарная сетка
   нам не нужна и её появление здесь было бы началом второго фреймворка.
   Шкала бутстраповская, чтобы совпадать с уже расставленными в шаблонах
   именами: 1 → 0.25rem, 2 → 0.5rem, 3 → 1rem, 4 → 1.5rem, 5 → 3rem.
   -------------------------------------------------------------------------- */

/* .mb-2 стоит в comments/comment_item.html и comments_list.html, но не был
   объявлен ни у нас, ни в Bootstrap 3 (в BS3 шкала отступов — .mbs/.mbm
   из LESS-миксинов, классов .mb-N нет). Отступ просто не применялся. */
.mb-2 {
    margin-bottom: 0.5rem !important;
}

.mb-3 {
    margin-bottom: 1rem !important;
}

.mb-4 {
    margin-bottom: 1.5rem !important;
}

.mt-3 {
    margin-top: 1rem !important;
}

.mt-4 {
    margin-top: 1.5rem !important;
}

.mt-5 {
    margin-top: 3rem !important;
}

.ms-2 {
    margin-left: 0.5rem !important;
}

.ms-4 {
    margin-left: 1.5rem !important;
}

.me-1 {
    margin-right: 0.25rem !important;
}

.d-flex {
    display: flex !important;
}

.justify-content-between {
    justify-content: space-between;
}

.justify-content-center {
    justify-content: center;
}

.align-items-start {
    align-items: flex-start;
}

.align-items-center {
    align-items: center;
}

.text-center {
    text-align: center;
}

.py-4 {
    padding-top: 1.5rem !important;
    padding-bottom: 1.5rem !important;
}

/* .gap-2 — в comment_item.html на .d-flex с двумя кнопками. Тоже не был
   объявлен нигде: кнопки слипались. */
.gap-2 {
    gap: 0.5rem !important;
}

/* .space-y-8 — тейлвиндовское наследие из прототипа, стоит обёрткой над
   списком карточек в blog/post_list, popular, favorite_articles. Класса не
   было ни в одном CSS проекта, то есть вертикальный ритм списка статей держался
   на собственных margin карточек. Объявляем по семантике Tailwind: отступ
   между соседями, а не вокруг всех. */
.space-y-8 > * + * {
    margin-top: 2rem;
}

/* .small и .lead — бутстраповские типографские утилиты, ещё стоящие в
   разметке (tutorials/_chapter_tasks, bookmark_list, main/tags). Объявлены
   здесь, чтобы снятие bootstrap.min.css их не унесло. */
.small {
    font-size: 0.875em;
}

.lead {
    font-size: 1.125rem;
    font-weight: 300;
    line-height: 1.5;
}

/* --------------------------------------------------------------------------
   Пагинация-список (<ul class="pagination"><li class="page-item">).
   Такую разметку использует одна страница — search/tags.html; остальные
   печатают плоский .page-links, объявленный выше. Ни .page-item, ни .page-link
   не были объявлены у нас, а BS 3.4.1 знает только `.pagination > li > a` —
   имена .page-item/.page-link из BS4. То есть страница держалась на
   `list-style` по умолчанию и рендерила пагинацию маркированным списком.
   -------------------------------------------------------------------------- */
ul.pagination {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    list-style: none;
    padding: 1rem 0 0;
    margin: 2rem 0 0;
}

.page-item {
    display: inline-flex;
}

.page-link {
    display: inline-flex;
    align-items: center;
    padding: 0.5rem 0.875rem;
    color: var(--primary);
    text-decoration: none;
    border: 1px solid var(--border-interactive);
    border-radius: 0.375rem;
    font-size: 0.9375rem;
}

a.page-link:hover {
    background-color: var(--accent);
    border-color: var(--primary);
}

/* Текущая страница — не ссылка, а <span>: подсвечиваем заливкой. */
.page-item.active .page-link {
    color: var(--primary-foreground);
    background-color: var(--primary-surface);
    border-color: var(--primary-surface);
}

/* --------------------------------------------------------------------------
   Вкладки профиля (accounts/profile.html, accounts/dashboard.html).
   Переключение делает JS-плагин Bootstrap (data-toggle="tab"), но САМО
   скрытие неактивных панелей — чисто CSS-правило, и у нас его не было:
   после снятия bootstrap.min.css обе панели показались бы разом.
   Селекторы повторяют семантику BS: скрыты только панели внутри .tab-content.
   -------------------------------------------------------------------------- */
.tab-content > .tab-pane {
    display: none;
}

.tab-content > .tab-pane.active {
    display: block;
}

/* Список пунктов навигации — повторяет `.nav` из Bootstrap 3.4.1, снятого на
   этапе 10.4. Без него <ul class="nav …"> снова получает браузерные умолчания
   <ul>: маркеры-точки и отступ 40px слева (замерено: list-style-type disc,
   padding-left 40px, margin-bottom 16px против none/0/0 до снятия Bootstrap).
   Затронуты profile_sidebar.html:24 (nav nav-pills nav-stacked) и вкладки
   в accounts/profile.html:35 и accounts/dashboard.html:56.

   ПОЧЕМУ ЗДЕСЬ, А НЕ В 01-base.css: 01 — это «голые» элементы и reset
   браузерных умолчаний; `.nav` же — класс-компонент, причём легаси-компонент
   Bootstrap, и лежать он должен рядом с остальными его остатками — .nav-tabs,
   .nav-stacked, .tab-content, — которые уже собраны в этом месте файла.
   Специфичность (0,1,0) — самая низкая из всех .nav-правил проекта, поэтому
   ни `.sidebar-links ul.nav`, ни `.profile-navigation .nav` не задеты. */
.nav {
    padding-left: 0;
    margin-bottom: 0;
    list-style: none;
}

/* Вертикальный список пунктов навигации в сайдбаре профиля. */
.nav-stacked > li {
    float: none;
    display: block;
}

.form-control {
    display: block;
    width: 100%;
    padding: 0.625rem 0.9375rem;
    font-size: 1.09375rem;
    font-weight: 400;
    line-height: 1.5;
    color: var(--foreground);
    background-color: var(--background);
    border: 1px solid var(--border-interactive);
    border-radius: var(--radius);
    transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}

.form-control:focus {
    color: var(--foreground);
    background-color: var(--background);
    border-color: var(--primary);
    outline: 0;
    box-shadow: 0 0 0 0.2rem oklch(var(--primary-raw) / 0.25);
}

.form-control-sm {
    padding: 0.25rem 0.5rem;
    font-size: 0.75rem;
    border-radius: calc(var(--radius) * 0.75);
}

.form-text,
.field__hint {
    margin-top: 0.25rem;
    font-size: 0.75rem;
    color: var(--muted-foreground);
}

/* Comments section styles */
.comments-section {
    background-color: var(--card);
    border-radius: var(--radius);
    padding: 1.5rem;
    border: 1px solid var(--border);
}

.comment-item .card {
    margin-bottom: 1rem;
}

.comment-author strong {
    color: var(--primary);
    font-weight: 600;
    font-size: 1rem;
}

.comment-content {
    color: var(--foreground);
    line-height: 1.7;
    font-size: 0.9375rem;
}

.reply-form {
    background-color: var(--muted);
    padding: 1rem;
    border-radius: var(--radius);
    border: 1px solid var(--border);
}

.replies {
    border-left: 2px solid var(--border);
    padding-left: 1rem;
}
/* Row system for similar posts */
.row {
    display: flex;
    flex-wrap: wrap;
    margin-left: -0.75rem;
    margin-right: -0.75rem;
}

.col-md-6 {
    flex: 0 0 auto;
    width: 100%;
    padding-left: 0.75rem;
    padding-right: 0.75rem;
}

@media (min-width: 768px) {
    .col-md-6 {
        width: 50%;
    }
}
/* Tag cloud in main content */
.main-content .tag-cloud-section {
  margin: 3rem auto 0 auto;
  padding: 2rem;
  /* Снятие легаси-моста: --bg-light/--border-color/--border-radius были
     алиасами на --card/--border/--radius, подставлены их значения один в один.
     --shadow алиаса не имел смысла как токен (одно значение на весь проект) и
     развёрнут в литерал — в дизайн-системе токена тени пока нет, см. TODO 10.4
     в static/css/pages/search.css. */
  background: var(--card);
  border-radius: var(--radius);
  border: 1px solid var(--border);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  max-width: 600px;
  width: 100%;
}

.main-content .section-title {
  text-align: center;
  margin-bottom: 1.5rem;
  color: var(--foreground);
  font-size: 1.5rem;
  font-weight: 600;
}

.main-content .tag-cloud-wrapper {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 300px;
  position: relative;
}

.main-content #tag_cloud {
  max-width: 100%;
  height: auto;
  border-radius: var(--radius);
}

.main-content .tag-cloud-fallback {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  justify-content: center;
  align-items: center;
  padding: 1rem;
}

/* Легаси-алиасы --accent-color/--primary-color здесь заливали плашку под белым
   текстом. Оба указывали на --primary, а это АКЦЕНТ (цвет ссылок и рамок): в
   тёмной теме он светлый, и белый текст поверх давал 2.63:1 при норме 4.5:1.
   Заливка под текстом — роль --primary-surface (5.70:1 в тёмной, 5.05:1 в
   светлой). Держим это в комментарии и после снятия моста: ошибка возвращается
   при каждой попытке «унифицировать» два токена в один. */
.main-content .tag-cloud-fallback .tag-link {
  display: inline-block;
  padding: 0.5rem 1rem;
  background: var(--primary-surface);
  color: white;
  text-decoration: none;
  border-radius: 20px;
  transition: all 0.3s ease;
  font-size: 0.9rem;
  font-weight: 500;
}

.main-content .tag-cloud-fallback .tag-link:hover {
  /* Ховер — тот же токен затемнения, что и у первичных кнопок: 7.01:1 в
     светлой теме, 7.62:1 в тёмной. */
  background: var(--primary-surface-hover);
  transform: translateY(-2px);
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}

/* Dark theme adjustments for main content tag cloud */
body[data-theme="dark"] .main-content .tag-cloud-section {
  /* Те же --card/--border, что и в базовом правиле: оба токена сами
     переключаются вместе с темой, так что правило — исторический дубль.
     Оставлен как есть: снятие моста меняет только ИМЕНА переменных,
     удаление мёртвых правил — работа 10.4. */
  background: var(--card);
  border-color: var(--border);
}

/* Тёмнотемных переопределений для .tag-link больше нет: --primary-surface и
   --primary-surface-hover сами меняются вместе с темой, а дубли под
   body[data-theme="dark"] только расходились бы с базовым правилом (именно так
   провал 2.63:1 и жил в двух местах сразу). */

/* Mobile responsiveness for main content tag cloud */
@media (max-width: 768px) {
  .main-content .tag-cloud-section {
    margin: 2rem auto 0 auto;
    padding: 1.5rem;
    max-width: 100%;
  }
  
  .main-content .tag-cloud-wrapper {
    min-height: 250px;
  }
  
  .main-content #tag_cloud {
    height: 250px;
  }
}

@media (max-width: 480px) {
  .main-content .tag-cloud-section {
    margin: 2rem auto 0 auto;
    padding: 1rem;
    max-width: 100%;
  }
  
  .main-content .tag-cloud-wrapper {
    min-height: 200px;
  }
  
  .main-content #tag_cloud {
    height: 200px;
  }
}
/* Header Nav Icons */
.header-nav a {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.header-nav a i {
    font-size: 1.2rem;
    width: 20px;
    text-align: center;
}

.header-nav a span {
    display: inline-block;
}

.header-nav a.active i {
    color: var(--primary);
}

/* Profile Components Mobile Styles */
@media (max-width: 768px) {
    /* Profile Container Mobile Styles */
    .profile-container {
        padding: 1rem 0 !important;
        width: 100% !important;
        margin: 0 !important;
        max-width: none !important;
    }
    
    .profile-container .row {
        margin: 0;
        width: 100%;
        max-width: none !important;
    }
    
    .profile-container .col-md-4,
    .profile-container .col-md-8 {
        width: 100%;
        padding-left: 0;
        padding-right: 0;
        margin: 0;
        max-width: none !important;
    }
    
    /* Ensure main container doesn't limit width on mobile */
    .container {
        max-width: none !important;
        padding-left: 1rem !important;
        padding-right: 1rem !important;
    }
    
    /* Profile Sidebar Mobile Styles */
    .profile-sidebar {
        width: 100% !important;
        margin: 0 0 1rem 0 !important;
        padding: 1.5rem !important;
        border-radius: var(--radius) !important;
        background-color: var(--card) !important;
        border: 1px solid var(--border) !important;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1) !important;
    }
    
    .profile-sidebar::before {
        display: none;
    }
    
    .profile-avatar {
        margin-bottom: 1rem !important;
    }
    
    .avatar-img,
    .avatar-placeholder {
        width: 80px !important;
        height: 80px !important;
    }
    
    .avatar-placeholder {
        font-size: 2rem !important;
    }
    
    .profile-name {
        font-size: 1.25rem !important;
        margin-bottom: 0.25rem !important;
    }
    
    .profile-username {
        font-size: 0.875rem !important;
        margin-bottom: 0.75rem !important;
    }
    
    .profile-bio {
        font-size: 0.875rem !important;
        margin-bottom: 1rem !important;
        line-height: 1.4 !important;
    }
    
    .profile-navigation {
        margin-top: 1rem !important;
    }
    
    .profile-navigation .nav > li {
        margin-bottom: 0.5rem !important;
    }
    
    .profile-navigation .nav > li > a {
        padding: 0.75rem 1rem !important;
        font-size: 0.875rem !important;
        border-radius: var(--radius) !important;
        background-color: var(--muted) !important;
        border: 1px solid var(--border) !important;
        color: var(--foreground) !important;
    }
    
    .profile-navigation .nav > li > a:hover {
        background-color: var(--accent) !important;
        border-color: var(--primary) !important;
        color: var(--primary) !important;
        transform: none !important;
    }
    
    .profile-navigation .nav > li.active > a {
        /* Активный пункт залит целиком, подпись пункта лежит поверх заливки. */
        background-color: var(--primary-surface) !important;
        color: var(--primary-foreground) !important;
        border-color: var(--primary-surface) !important;
        transform: none !important;
        box-shadow: 0 2px 8px rgba(52, 152, 219, 0.3) !important;
    }
    
    .profile-navigation .nav > li > a i {
        font-size: 0.875rem !important;
        width: 16px !important;
    }
    
    /* Profile Stats Card Mobile Styles */
    .profile-stats-card {
        width: 100% !important;
        margin: 0 0 1rem 0 !important;
        padding: 1.5rem !important;
        border-radius: var(--radius) !important;
        background-color: var(--card) !important;
        border: 1px solid var(--border) !important;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1) !important;
    }
    
    .profile-stats-card::before {
        display: none;
    }
    
    .stats-title {
        font-size: 1.125rem !important;
        margin-bottom: 1rem !important;
        padding-bottom: 0.5rem !important;
    }
    
    .stats-title::after {
        width: 30px !important;
        height: 2px !important;
    }
    
    .profile-stats {
        flex-direction: row !important;
        justify-content: space-around !important;
        gap: 1rem !important;
    }
    
    .stat-item {
        flex: 1 !important;
        text-align: center !important;
    }
    
    .stat-number {
        font-size: 1.5rem !important;
        margin-bottom: 0.25rem !important;
        color: var(--primary) !important;
    }
    
    .stat-label {
        font-size: 0.75rem !important;
        color: var(--muted-foreground) !important;
    }
    
    /* Profile Content Mobile Styles */
    .profile-content {
        width: 100% !important;
        margin: 0 !important;
        padding: 1.5rem !important;
        border-radius: var(--radius) !important;
        background-color: var(--card) !important;
        border: 1px solid var(--border) !important;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1) !important;
    }
    
    .profile-content::before {
        display: none;
    }
    
    .profile-tabs .nav-tabs {
        margin-bottom: 1.5rem !important;
        padding: 0.5rem !important;
        background-color: var(--muted) !important;
        border-radius: var(--radius) !important;
        border-bottom: none !important;
    }
    
    .profile-tabs .nav-tabs::before {
        display: none;
    }
    
    .profile-tabs .nav-tabs > li {
        flex: 1;
    }
    
    .profile-tabs .nav-tabs > li > a {
        padding: 0.75rem 0.5rem !important;
        font-size: 0.875rem !important;
        border-radius: var(--radius) !important;
        margin-right: 0.25rem !important;
        background-color: transparent !important;
        color: var(--muted-foreground) !important;
        text-align: center;
    }
    
    .profile-tabs .nav-tabs > li > a:hover {
        background-color: var(--accent) !important;
        color: var(--foreground) !important;
        transform: none !important;
    }
    
    .profile-tabs .nav-tabs > li.active > a {
        background-color: var(--primary-surface) !important;
        color: var(--primary-foreground) !important;
        transform: none !important;
        box-shadow: 0 2px 8px rgba(52, 152, 219, 0.3) !important;
    }
    
    .profile-details h4,
    .profile-activity h4 {
        font-size: 1.25rem !important;
        margin-bottom: 1rem !important;
        padding-bottom: 0.5rem !important;
    }
    
    .profile-details h4::after,
    .profile-activity h4::after {
        width: 40px !important;
        height: 2px !important;
    }
    
    .detail-grid {
        gap: 0.75rem !important;
        padding: 0 !important;
    }
    
    .detail-item {
        padding: 1rem !important;
        border-radius: var(--radius) !important;
        background-color: var(--muted) !important;
        border: 1px solid var(--border) !important;
        flex-direction: column !important;
        align-items: flex-start !important;
        gap: 0.5rem !important;
    }
    
    .detail-item:hover {
        transform: none !important;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1) !important;
    }
    
    .detail-item::before {
        display: none;
    }
    
    .detail-label {
        min-width: auto !important;
        font-size: 0.75rem !important;
        margin-bottom: 0.25rem !important;
        color: var(--muted-foreground) !important;
    }
    
    .detail-label::before {
        display: none;
    }
    
    .detail-value {
        font-size: 0.875rem !important;
        color: var(--foreground) !important;
    }
    
    .profile-activity .text-muted {
        padding: 1.5rem 1rem !important;
        font-size: 0.875rem !important;
        border-radius: var(--radius) !important;
        background-color: var(--muted) !important;
        border: 1px solid var(--border) !important;
    }
    
    .profile-activity .text-muted::before {
        font-size: 2rem !important;
        margin-bottom: 0.75rem !important;
    }
}

/* Dark theme adjustments for mobile profile components */
@media (max-width: 768px) {
    body[data-theme="dark"] .profile-sidebar,
    body[data-theme="dark"] .profile-stats-card,
    body[data-theme="dark"] .profile-content {
        background-color: var(--card) !important;
        border-color: var(--border) !important;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3) !important;
    }
    
    body[data-theme="dark"] .profile-navigation .nav > li > a {
        background-color: var(--muted) !important;
        border-color: var(--border) !important;
        color: var(--foreground) !important;
    }
    
    body[data-theme="dark"] .profile-navigation .nav > li > a:hover {
        background-color: var(--accent) !important;
        border-color: var(--primary) !important;
        color: var(--primary) !important;
    }
    
    body[data-theme="dark"] .profile-navigation .nav > li.active > a {
        background-color: var(--primary-surface) !important;
        color: var(--primary-foreground) !important;
        border-color: var(--primary-surface) !important;
        box-shadow: 0 2px 8px rgba(46, 204, 113, 0.3) !important;
    }
    
    body[data-theme="dark"] .profile-tabs .nav-tabs {
        background-color: var(--muted) !important;
    }
    
    body[data-theme="dark"] .profile-tabs .nav-tabs > li > a {
        color: var(--muted-foreground) !important;
    }
    
    body[data-theme="dark"] .profile-tabs .nav-tabs > li > a:hover {
        background-color: var(--accent) !important;
        color: var(--foreground) !important;
    }
    
    body[data-theme="dark"] .profile-tabs .nav-tabs > li.active > a {
        background-color: var(--primary-surface) !important;
        color: var(--primary-foreground) !important;
        box-shadow: 0 2px 8px rgba(46, 204, 113, 0.3) !important;
    }
    
    body[data-theme="dark"] .detail-item {
        background-color: var(--muted) !important;
        border-color: var(--border) !important;
    }
    
    body[data-theme="dark"] .detail-item:hover {
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3) !important;
    }
    
    body[data-theme="dark"] .profile-activity .text-muted {
        background-color: var(--muted) !important;
        border-color: var(--border) !important;
    }
}

/* Old styles removed - now using .theme-toggle-btn.mobile-theme-toggle instead */


/* ==========================================================================
   Pixel cat mascot
   ========================================================================== */


.pixel-cat {
  display: inline-block;
  image-rendering: pixelated;
  vertical-align: middle;
  overflow: visible; /* let Zzz / tail spill past the viewBox */
}

.pc-fur   { fill: var(--cat-fur); }
.pc-ear   { fill: var(--cat-ear); }
.pc-eye   { fill: var(--cat-eye); }
.pc-iris  { fill: var(--cat-iris); }
.pc-blush { fill: var(--cat-blush); }

/* ==========================================================================
   Pixel oak tree — companion mascot to the cat (cat + oak = DevDao header)
   Same 18×18 grid and crispEdges technique as the cat. Natural palette:
   green canopy + brown trunk. Dark theme lifts the greens so the tree
   reads against the dark background.
   ========================================================================== */


.pixel-oak {
  display: inline-block;
  image-rendering: pixelated;
  vertical-align: middle;
}

.po-leaf   { fill: var(--oak-leaf); }
.po-leaf2  { fill: var(--oak-leaf2); }
.po-trunk  { fill: var(--oak-trunk); }
.po-trunk2 { fill: var(--oak-trunk2); }

/* Hide all the mood-specific bits by default; each mood re-enables its own. */
.pc-eye-closed,
.pc-zzz,
.pc-quest,
.pc-glass { display: none; }

/* --- Mood: sit (default) --- */
.pixel-cat--sit .pc-tail {
  transform-box: fill-box;
  transform-origin: bottom left;
  animation: pc-wag 1.6s ease-in-out infinite;
}

/* --- Mood: sleep --- */
.pixel-cat--sleep .pc-eye-open { display: none; }
.pixel-cat--sleep .pc-eye-closed { display: block; }
.pixel-cat--sleep .pc-zzz {
  display: block;
  transform-box: fill-box;
  transform-origin: center;
  animation: pc-float 2.4s ease-in-out infinite;
}

/* --- Mood: lost --- */
.pixel-cat--lost .pc-quest {
  display: block;
  transform-box: fill-box;
  transform-origin: center;
  animation: pc-float 2s ease-in-out infinite;
}
.pixel-cat--lost { animation: pc-tilt 3s ease-in-out infinite; }

/* --- Mood: search --- */
.pixel-cat--search .pc-glass { display: block; }

@keyframes pc-wag {
  0%, 100% { transform: rotate(0deg); }
  50%      { transform: rotate(-16deg); }
}
@keyframes pc-float {
  0%, 100% { transform: translateY(0); opacity: 0.85; }
  50%      { transform: translateY(-1px); opacity: 1; }
}
@keyframes pc-tilt {
  0%, 100% { transform: rotate(-3deg); }
  50%      { transform: rotate(3deg); }
}
@keyframes pc-bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-8px); }
}

@media (prefers-reduced-motion: reduce) {
  .pixel-cat,
  .pixel-cat * { animation: none !important; }
}

/* --- Logo cat: sleeps when the dark theme is on --- */
.header-logo .pixel-cat {
  width: 26px;
  height: 26px;
  margin-right: 2px;
}
body[data-theme="dark"] .header-logo .pc-eye-open { display: none; }
body[data-theme="dark"] .header-logo .pc-eye-closed { display: block; }
body[data-theme="dark"] .header-logo .pc-tail { animation: none; }
/* Spewing Zzz bubbles: cat fully asleep in dark theme */
body[data-theme="dark"] .header-logo .pc-zzz {
  display: block;
  transform-box: fill-box;
  transform-origin: center;
  animation: pc-float 2.4s ease-in-out infinite;
}

/* --- Logo oak: sized to match the cat in the header --- */
.header-logo .pixel-oak {
  width: 26px;
  height: 26px;
  margin-left: 2px;
}

/* --- Footer cat: small, sitting in the corner --- */
.footer-cat {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-top: 0.75rem;
  color: var(--muted-foreground);
  font-size: 0.85rem;
}
.footer-cat .pixel-cat { width: 40px; height: 40px; }

/* --- Empty state with cat --- */
.cat-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 0.75rem;
  padding: 2.5rem 1rem;
  color: var(--muted-foreground);
}
.cat-empty .pixel-cat { width: 96px; height: 96px; }
.cat-empty .cat-empty-title {
  margin: 0;
  font-size: 1.15rem;
  font-weight: 600;
  color: var(--foreground);
}
.cat-empty p { margin: 0; }

/* --- 404 / error page --- */
.cat-error {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 1rem;
  padding: 3rem 1rem 4rem;
  min-height: 50vh;
}
.cat-error .pixel-cat {
  width: 180px;
  height: 180px;
  animation: pc-bob 3s ease-in-out infinite;
}
.cat-error .cat-error-code {
  font-family: var(--font-mono);
  font-size: clamp(3rem, 12vw, 6rem);
  font-weight: 700;
  line-height: 1;
  margin: 0;
  color: var(--primary);
}
.cat-error h1 {
  margin: 0;
  font-size: 1.5rem;
  color: var(--foreground);
}
.cat-error p {
  margin: 0;
  max-width: 32rem;
  color: var(--muted-foreground);
}
.cat-error .cat-speech {
  position: relative;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.5rem 0.9rem;
  font-family: var(--font-mono);
  font-size: 0.9rem;
  color: var(--foreground);
}
.cat-error .cat-speech::after {
  content: "";
  position: absolute;
  bottom: -8px;
  left: 50%;
  transform: translateX(-50%);
  border-width: 8px 8px 0 8px;
  border-style: solid;
  border-color: var(--card) transparent transparent transparent;
}
.cat-error .cat-error-actions {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: 0.5rem;
}
/* ==========================================================================
   FORMS
   Reusable form primitives so every form (auth, contacts, comments, search)
   looks consistent and adapts to the theme. Django often renders <input>
   without widget classes, so we style the native elements directly and let
   .form-control opt in explicitly where templates already use it.
   ========================================================================== */

/* .field — наше имя для обёртки поля (подпись + контрол + подсказка + ошибка).
   Объявлено ОБЩИМ селектором с бутстраповским .form-group, а не отдельным
   набором правил: в разметке сейчас 22 .form-group, переверстают их не в этом
   этапе, и два независимых определения гарантированно разъехались бы. */
.field,
.form-group {
  margin-bottom: 1.25rem;
}
.field > label,
.field__label,
.form-group > label,
.auth-form label {
  display: block;
  font-weight: 500;
  font-size: 0.9375rem;
  color: var(--foreground);
  margin-bottom: 0.375rem;
}

/* Native text-like inputs + textarea + select, scoped to known form contexts
   so we don't restyle unrelated controls (e.g. the sort <select>). */
.auth-form input[type="text"],
.auth-form input[type="email"],
.auth-form input[type="password"],
.auth-form input[type="url"],
.auth-form textarea,
.auth-form select,
.contact-form input[type="text"],
.contact-form input[type="email"],
.contact-form input[type="password"],
.contact-form textarea,
.comment-form input[type="text"],
.comment-form input[type="email"],
.comment-form textarea,
.form-control {
  width: 100%;
  display: block;
  padding: 0.625rem 0.875rem;
  font-size: 1rem;
  line-height: 1.5;
  font-family: inherit;
  color: var(--foreground);
  background-color: var(--background);
  border: 1px solid var(--border-interactive);
  border-radius: 0.5rem;
  transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
  -webkit-appearance: none;
  appearance: none;
}
.auth-form textarea,
.contact-form textarea,
.comment-form textarea {
  min-height: 7.5rem;
  resize: vertical;
}

.auth-form input:focus,
.contact-form input:focus,
.comment-form input:focus,
.auth-form textarea:focus,
.contact-form textarea:focus,
.comment-form textarea:focus,
.auth-form select:focus,
.form-control:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px oklch(var(--primary-raw) / 0.25);
  background-color: var(--card);
}
.auth-form input::placeholder,
.contact-form input::placeholder,
.form-control::placeholder {
  color: var(--muted-foreground);
}

/* Inline validation errors (Django renders <ul> inside .form-errors).
   .field__error — то же самое под именем нового компонента. */
.field__error,
.form-errors {
  /* Текстовый красный, а не заливочный: --destructive здесь не проходил AA
     (4.26:1 на тёмной карточке, ~4.4:1 на светлом фоне). */
  color: var(--destructive-text);
  font-size: 0.8125rem;
  margin-top: 0.375rem;
}
.field__error ul,
.form-errors ul {
  margin: 0;
  padding-left: 1.125rem;
}

/* Primary action button — a flat, theme-aware alternative to the hard-coded
   blue/green gradient buttons in the auth <style> blocks. Templates can add
   the .btn-flat class to opt in. */
.btn-flat {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.75rem 1.25rem;
  font-size: 1rem;
  font-weight: 600;
  color: var(--primary-foreground);
  background-color: var(--primary-surface);
  border: none;
  border-radius: 0.5rem;
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.2s ease,
    background-color 0.2s ease;
}
.btn-flat:hover {
  /* Было `filter: brightness(1.05)` — то же осветление, что и альфа у прочих
     первичных кнопок, и с тем же результатом: в светлой теме текст поверх
     падал с 4.77:1 до 4.40:1, ниже AA. Ховер теперь затемняет (6.63:1). */
  background-color: var(--primary-surface-hover);
  /* Свечение — это ореол самой кнопки, поэтому его оттенок берётся с той же
     -surface-raw: с --primary-raw он бы «светился» другим синим, чем заливка. */
  box-shadow: 0 6px 16px oklch(var(--primary-surface-raw) / 0.35);
}
.btn-flat:active {
  transform: translateY(1px);
}
.btn-flat:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--background), 0 0 0 4px var(--ring);
}

/* Auth card layout (login / register). Previously each template carried a
   250-line inline <style> with hard-coded blue/green gradients and tokens
   that no longer exist; consolidated here against the real tokens. */
.auth-container {
  padding: 2rem 0;
  min-height: 60vh;
  width: 100%;
  max-width: 480px;
  margin: 0 auto;
}
.auth-card {
  background-color: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 2rem;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
.auth-header {
  text-align: center;
  margin-bottom: 1.5rem;
}
.auth-title {
  font-size: 1.5rem;
  font-weight: 600;
  color: var(--foreground);
  margin: 0 0 0.375rem;
}
.auth-subtitle {
  color: var(--muted-foreground);
  margin: 0;
  font-size: 0.9375rem;
}
.auth-footer {
  text-align: center;
  margin-top: 1.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
}
.auth-footer p {
  color: var(--muted-foreground);
  margin: 0;
}
.auth-footer a {
  color: var(--primary);
  font-weight: 500;
  text-decoration: none;
}
.auth-footer a:hover {
  text-decoration: underline;
}

/* Социальная авторизация: разделитель «или» и кнопка VK. */
.social-auth-divider {
  display: flex;
  align-items: center;
  text-align: center;
  margin: 1.25rem 0;
  color: var(--muted-foreground);
}
.social-auth-divider::before,
.social-auth-divider::after {
  content: '';
  flex: 1;
  border-bottom: 1px solid var(--border);
}
.social-auth-divider span {
  padding: 0 0.75rem;
  font-size: 0.85rem;
}
/* Кнопка «войти через ВКонтакте». Заливка и текст — токенами, а не хардкодами
   #0077FF / #fff: старая пара давала 4.13:1 при норме 4.5:1, и ни один из двух
   цветов не реагировал на смену темы. Подбор новых значений и все замеры —
   в 00-tokens.css рядом с --social-vk. Здесь важно лишь то, что покой
   (6.08:1) контрастнее ховера (4.92:1), а не наоборот, как было. */
.vk-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  width: 100%;
  background-color: var(--social-vk);
  /* Тот же токен, что и у остальных залитых кнопок системы (.btn-primary):
     текст на брендовой заливке ничем не отличается от текста на --primary-surface,
     и заводить ему отдельный --social-vk-foreground не за чем. */
  color: var(--primary-foreground);
  text-decoration: none;
  padding: 0.7rem 1rem;
  border-radius: 0.5rem;
  font-weight: 500;
  font-size: 0.9375rem;
  transition: background-color 0.2s ease;
}
.vk-btn:hover {
  background-color: var(--social-vk-hover);
  color: var(--primary-foreground);
}
/* Нажатие. Цвет возвращается к самому тёмному и самому читаемому варианту
   (покой), а обратная связь идёт сдвигом на пиксель — ровно как у .btn:active
   в этом же файле. Третий оттенок ради этого не заводится: он либо повторил бы
   покой, либо ушёл ещё светлее ховера и упёрся в норму 4.5:1. */
.vk-btn:active {
  background-color: var(--social-vk);
  transform: translateY(1px);
}
/* Обводка фокуса — var(--ring), как у .button/.btn:focus-visible. Брендовый
   синий тут не годится: с outline-offset обводка лежит НЕ на кнопке, а на
   карточке позади неё, и мерить её надо с карточкой. --social-vk дал бы на
   тёмной карточке 3.15:1 (впритык), --ring даёт 5.25:1 в светлой теме и
   7.73:1 в тёмной, потому что сам зависит от темы. */
.vk-btn:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

/* Checkbox + label row (remember me). */
.form-check {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.form-check input[type="checkbox"] {
  width: 1.0625rem;
  height: 1.0625rem;
  /* accent-color заливает КВАДРАТ чекбокса, а галочку браузер рисует поверх
     светлым — то есть это поверхность, а не акцентный цвет линии. */
  accent-color: var(--primary-surface);
  cursor: pointer;
}
.form-check-label {
  margin: 0;
  font-size: 0.9375rem;
  color: var(--foreground);
  cursor: pointer;
}

@media (max-width: 480px) {
  .auth-card {
    padding: 1.5rem 1.25rem;
  }
}

/* Wider variant for the registration form, which has side-by-side columns. */
.auth-container-wide {
  max-width: 600px;
}
/* Django help_text under form fields (password requirements, etc.). */
.form-text,
small.text-muted {
  display: block;
  margin-top: 0.375rem;
  font-size: 0.8125rem;
  color: var(--muted-foreground);
}

/* ==========================================================================
   jstree — custom theme for the categories tree (/search/categories/)
   Replaces the default "yellow folder" theme with a clean look built on the
   project's design tokens. The default theme sprites (folder icons, blue
   triangles) are hidden; expand/collapse is drawn with a CSS chevron instead.
   ========================================================================== */
.category-tree .jstree {
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  color: var(--foreground);
  background: transparent !important;
  padding: 0.25rem 0;
}
.category-tree .jstree-node {
  /* reset default left padding; indentation comes from nested <ul> margins */
  padding-left: 0;
}
.category-tree .jstree-children {
  margin-left: 1.125rem;
  border-left: 1px solid var(--border);
  padding-left: 0.5rem;
}
.category-tree .jstree-anchor {
  /* inline-flex (not flex!) so the link stays on the SAME line as the
     .jstree-ocl toggle that precedes it — they are siblings, not nested. */
  display: inline-flex !important;
  align-items: center;
  vertical-align: middle;
  padding: 0.375rem 0.625rem;
  margin: 1px 0;
  border-radius: 0.375rem;
  color: var(--foreground);
  text-decoration: none;
  font-weight: 500;
  line-height: 1.4;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.category-tree .jstree-anchor:hover {
  background-color: var(--accent);
  color: var(--foreground);
}
.category-tree .jstree-anchor.jstree-clicked,
.category-tree .jstree-anchor[aria-selected="true"] {
  background-color: oklch(var(--primary-raw) / 0.12);
  /* Тот же случай, что и у .tag-count: --primary поверх собственного 12%-го
     тинта. После понижения светлоты до 0.52 в светлой теме получается 4.44:1 —
     всё ещё ниже 4.5:1, поэтому текст нейтральный (17.38:1 светлая, 15.43:1
     тёмная). Чтобы выбранный узел не потерял отличие от невыбранных теперь,
     когда цвет текста у них общий, состояние держится на плашке и жирности. */
  color: var(--foreground);
  font-weight: 600;
}

/* Hide the default folder/file theme icons — the label text is enough. */
.category-tree .jstree-themeicon {
  display: none !important;
}

/* Expand/collapse toggle (.jstree-ocl): replace the sprite triangle with a
   clean CSS chevron that rotates when the node is open. */
.category-tree .jstree-ocl {
  display: inline-block;
  vertical-align: middle;
  width: 1.125rem;
  height: 1.125rem;
  margin-right: 0.25rem;
  background: none !important;
  position: relative;
  cursor: pointer;
  border-radius: 0.25rem;
  transition: background-color 0.15s ease;
}
.category-tree .jstree-ocl:hover {
  background-color: var(--muted);
}
/* chevron drawn with two borders, points right (collapsed) */
.category-tree .jstree-ocl::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0.375rem;
  height: 0.375rem;
  border-right: 2px solid var(--muted-foreground);
  border-bottom: 2px solid var(--muted-foreground);
  transform: translate(-65%, -75%) rotate(-45deg);
  transition: transform 0.2s ease;
}
/* open node → rotate chevron to point down */
.category-tree .jstree-open > .jstree-ocl::before {
  transform: translate(-65%, -35%) rotate(45deg);
}
/* leaf nodes (no children) get no toggle chevron */
.category-tree .jstree-leaf > .jstree-ocl::before {
  content: none;
}
.category-tree .jstree-leaf > .jstree-ocl {
  cursor: default;
}

/* wholerow plugin: keep its hover background subtle and on-theme. */
.category-tree .jstree-wholerow {
  height: 2rem;
  border-radius: 0.375rem;
}
.category-tree .jstree-wholerow-hovered {
  background-color: var(--accent);
}
.category-tree .jstree-wholerow-clicked {
  background-color: oklch(var(--primary-raw) / 0.12);
}

/* Count badge "Title (N)" is part of the text; nothing extra needed, but
   tighten the wrap on narrow screens. */
@media (max-width: 767px) {
  .category-tree .jstree-children {
    margin-left: 0.75rem;
  }
  .category-tree .jstree-anchor {
    padding: 0.5rem 0.5rem;
  }
}

/* ==========================================================================
   СОЦИАЛЬНЫЕ ССЫЛКИ ПОДВАЛА — исправление контраста ховера.

   Базовые правила .social-link.<сеть>:hover лежат в 02-layout.css вместе с
   остальным подвалом, и там же были захардкожены фирменные цвета сетей.
   Замеры на градиенте подвала (--card → --muted):
       #1da1f2  twitter   светлая 2.83:1 / 2.46:1   (норма 4.5:1)
       #0088cc  telegram  светлая 3.89:1 / 3.38:1
       #ff6600  rss       светлая 2.94:1 / 2.55:1
       #333     github    ТЁМНАЯ  1.60:1 — наведение курсора ссылку почти гасило
   То есть в светлой теме проваливались три сети из четырёх, а в тёмной —
   четвёртая. Причина одна: брендовый цвет один на обе темы, а подложка меняется.

   Правила переопределены ЗДЕСЬ, а не поправлены на месте, потому что этап
   10.4 ограничен файлами 03-components.css и 00-tokens.css (02-layout.css
   параллельно переверстывают под собственную сетку). Механика перекрытия:
   специфичность совпадает с исходными правилами, 03-components.css
   подключается ПОЗЖЕ 02-layout.css, поэтому при равном !important выигрывает
   этот блок. !important здесь обязателен — он стоит и в исходных правилах.
   Когда 02-layout.css освободится, хардкоды оттуда надо удалить, а этот блок
   перенести на их место.
   ========================================================================== */
.social-link.github:hover,
.social-link.github:hover svg {
  color: var(--social-github) !important;
}

.social-link.twitter:hover,
.social-link.twitter:hover svg {
  color: var(--social-twitter) !important;
}

.social-link.telegram:hover,
.social-link.telegram:hover svg {
  color: var(--social-telegram) !important;
}

.social-link.rss:hover,
.social-link.rss:hover svg {
  color: var(--social-rss) !important;
}

/* Довесок к блоку выше, найденный при перепроверке этапа 10.4. В 02-layout.css
   у GitHub есть ВТОРАЯ пара правил — под `body[data-theme="dark"]`, с хардкодом
   #f0f6fc. Её специфичность (0,3,1) выше, чем у правила `.social-link.github:hover`
   выше (0,2,1), поэтому в тёмной теме токен --social-github не применялся вовсе:
   выигрывал хардкод. Контраст при этом не страдал (#f0f6fc даёт 18.65:1 на
   тёмном подвале против 16.02:1 у токена) — то есть провала не было, но токен
   в тёмной теме был мёртвым, и правка --social-github ни на что не влияла.
   Лечится тем же приёмом, что и весь блок: повторяем селектор с той же
   специфичностью, а выигрываем за счёт порядка подключения (03 после 02).
   Когда 02-layout.css освободится — удалить оттуда обе пары вместе с этой. */
body[data-theme="dark"] .social-link.github:hover,
body[data-theme="dark"] .social-link.github:hover svg {
  color: var(--social-github) !important;
}
