/* =========================================================================
 * pages/snippets.css — стили раздела «Копилка» (codskl)
 *
 * Раздел был выпущен без единого правила: все ~80 классов из шаблонов
 * codskl/ (snippet-gallery-*, snippet-card-*, my-snippets-*, snippets-trash-*,
 * snippet-detail/-header/-code, snippet-form-*, snippet-delete-*) не имели
 * ни одного объявления ни в ядре, ни в pages/. Страницы рендерились голым
 * HTML: списки с буллетами, блоки кода без границ и ограничения высоты,
 * панель фильтров с полями произвольной ширины, уезжающая под сайдбар.
 *
 * Подключается через {% block extra_css %} в семи шаблонах раздела — так же,
 * как pages/tutorials.css у курсов. Общий для всех страниц codskl файл, а не
 * семь инлайновых <style>: у витрины и «Моих сниппетов» одинаковые панель
 * фильтров и карточка, и два независимых определения разъехались бы.
 *
 * Восьмой шаблон, snippet_embed.html, сюда НЕ входит: это партиал, который
 * разворачивается шорткодом в теле статьи или главы курса, а те грузят свои
 * pages/*.css. Его стили — и стили общего js/code-extras.js — живут в
 * 03-components.css, то есть в ядре, которое есть на каждой странице.
 *
 * ПРАВИЛО ПРОЕКТА: никаких var(--токен, #fallback). Все использованные
 * токены объявлены в 00-tokens.css и в :root, и в :root[data-theme="dark"],
 * поэтому fallback страховал бы только от опечатки в имени переменной —
 * ровно так однажды спрятался баг тёмной темы в главе курса (см. шапку
 * pages/tutorials.css).
 *
 * ПРО ТЁМНУЮ ТЕМУ: отдельной секции под неё здесь нет и не нужно — цвета
 * берутся токенами, которые тема переопределяет сама. Единственное место,
 * где тема упомянута явно, — нумерация строк на странице сниппета
 * (line-numbers ниже): у неё в обеих темах свои константы прозрачности,
 * как и в 04-prose.css для кода внутри статьи.
 * ========================================================================= */


/* =========================================================================
 * 1. ОБЩЕЕ ДЛЯ РАЗДЕЛА
 * ========================================================================= */

/* Шапка с кнопками — «Мои сниппеты» и «Корзина»: заголовок слева, действия
   справа.

   В разметке контейнер стоит с `d-flex gap-2`, а `.d-flex` объявлен как
   `display:flex !important` — переопределить его на grid нельзя, поэтому
   раскладка достраивается свойствами, которые с флексом совместимы.
   `align-items:center` здесь не косметика: по умолчанию у флекса
   `align-items:stretch`, и <a class="btn"> растягивался на всю высоту
   строки <h1> — именно отсюда брались кнопки высотой в три сантиметра
   рядом с заголовком «Мои сниппеты». */
.my-snippets-header,
.snippets-trash-header {
    align-items: center;
    flex-wrap: wrap;
    row-gap: 0.75rem;
    margin-top: 1.5rem;
}

/* Заголовок забирает свободное место, кнопки прижимаются к правому краю. */
.my-snippets-title,
.snippets-trash-title {
    flex: 1 1 auto;
}

/* Шапка витрины кнопок не содержит и флексом не является: это заголовок с
   лидом под ним. В разметке на ней `space-y-8` (2rem между соседями) —
   для такой пары это разрыв, а не ритм. */
.snippet-gallery-header {
    margin-top: 1.5rem;
}
.snippet-gallery-header.space-y-8 > * + * {
    margin-top: 0.5rem;
}
.snippet-gallery-lead {
    max-width: 60ch;
}

.snippet-gallery-title,
.my-snippets-title,
.snippets-trash-title {
    font-size: 2rem;
    font-weight: 700;
    line-height: 1.2;
}

/* --- Панель фильтров (витрина и «Мои сниппеты») --------------------------- */

/* Общий класс обеих панелей. Текстовые поля тут — нативные <input> без
   .form-control: рамку, радиус и кольцо фокуса им выдаёт общий блок FORMS
   в 03-components.css (контексты .snippet-filters добавлены туда же), а этот
   файл отвечает за раскладку. <select> — исключение, он оформлен ниже прямо
   здесь: общий блок снимает нативную отрисовку вместе со стрелкой. */
.snippet-filters {
    align-items: flex-end;
    flex-wrap: wrap;
    row-gap: 0.75rem;
    padding: 1rem;
    margin-top: 1.25rem;
    background-color: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

/* Каждое поле — колонка «подпись над контролом». `min-width: 0` обязателен:
   без него flex-элемент не может стать уже своего min-content, и панель
   с пятью полями (у «Моих сниппетов») распирала колонку контента вправо,
   заезжая под сайдбар — на скриншоте кнопка «Фильтровать» была обрезана
   ровно поэтому. */
.snippet-filters label {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    flex: 1 1 10rem;
    /* Потолок нужен последнему полю в ряду: без него одинокая «Сортировка»,
       перенесённая на вторую строку, растягивалась во всю ширину панели и
       читалась как поле другого назначения. */
    max-width: 20rem;
    min-width: 0;
    margin-bottom: 0;
    font-size: 0.9375rem;
}

/* Поле поиска шире прочих: в него пишут фразу, а не одно слово. */
.snippet-filters label:has(input[type="search"]) {
    flex-basis: 16rem;
}

/* Кнопки не тянутся и садятся на нижний край ряда полей. */
.snippet-filters .btn {
    flex: 0 0 auto;
}

/* Селекты раздела. Повторяют геометрию текстовых полей из блока FORMS
   (03-components.css), но БЕЗ appearance:none: тот список специально не
   трогает <select>, потому что снятая нативная отрисовка уносит стрелку, а
   своей взамен не появляется — выпадающий список становится неотличим от
   поля ввода. Здесь тот же радиус, рамка и подложка при сохранённой
   стрелке. */
.snippet-filters select,
.snippet-form select {
    width: 100%;
    padding: 0.5rem 0.75rem;
    font-family: inherit;
    font-size: 1rem;
    line-height: 1.5;
    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;
}
.snippet-filters select:focus,
.snippet-form select:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 3px oklch(var(--primary-raw) / 0.25);
}

/* --- Пустое состояние ----------------------------------------------------- */

.snippet-gallery-empty,
.my-snippets-empty,
.snippets-trash-empty {
    padding: 2.5rem 1.5rem;
    margin-top: 1.25rem;
    text-align: center;
    color: var(--muted-foreground);
    background-color: var(--card);
    border: 1px dashed var(--border-interactive);
    border-radius: var(--radius);
}
.snippet-gallery-empty p,
.my-snippets-empty p,
.snippets-trash-empty p {
    margin: 0;
}

/* --- Списки раздела ------------------------------------------------------- */

/* Все три списка — семантические <ul>, но визуально это колонка карточек,
   а не перечисление: маркеры и левый отступ убираем. */
.snippet-gallery-list,
.my-snippets-list,
.snippets-trash-list {
    list-style: none;
    margin: 1.25rem 0 0;
    padding: 0;
    display: grid;
    /* Именно minmax(0, 1fr), а не подразумеваемый `auto`. Неявная колонка
       грида имеет максимум max-content, и блок кода в карточке (<pre> с
       собственным overflow) раздувал её до длины самой длинной строки: на
       390px карточки уезжали за правый край экрана вместе с заголовками.
       Нулевой минимум разрешает колонке быть уже своего содержимого, и
       прокрутка остаётся внутри <pre>, где ей и место. */
    grid-template-columns: minmax(0, 1fr);
    gap: 0.75rem;
}

/* --- Блок кода в карточке и на странице ----------------------------------- */

/* Класс языка у сниппетов стоит на <code>, а не на <pre>, поэтому подложку
   от Prism (`pre[class*="language-"]`) эти блоки не получают — красим сами
   тем же токеном --prism-bg, каким красит её 04-prose.css внутри статьи. */
.snippet-card-code,
.snippet-code pre {
    background: var(--prism-bg);
    border: 1px solid var(--border);
    border-radius: 0.5rem;
    padding: 0.875rem 1rem;
    margin: 0;
    font-family: var(--font-mono);
    font-size: 0.8125rem;
    line-height: 1.6;
    overflow: auto;
}


/* =========================================================================
 * 2. ВИТРИНА — codskl/gallery.html
 * ========================================================================= */

.snippet-card {
    padding: 1rem;
    background-color: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}
.snippet-card:hover {
    border-color: var(--border-interactive);
}

.snippet-card > header {
    align-items: baseline;
    row-gap: 0.375rem;
}

/* Длинное название обязано переноситься, а не распирать карточку: у flex-
   элемента min-width по умолчанию auto, то есть он не может стать уже своего
   min-content. */
.snippet-card > header > .snippet-card-title {
    min-width: 0;
}

.snippet-card-title {
    font-size: 1.0625rem;
    font-weight: 600;
    color: var(--foreground);
    text-decoration: none;
}
.snippet-card-title:hover {
    color: var(--primary);
    text-decoration: underline;
}

/* Язык — не ссылка, а метка, поэтому нейтральная плашка без ховера. */
.snippet-card-language {
    flex: 0 0 auto;
}

.snippet-card-meta {
    margin-top: 0.375rem;
    font-size: 0.875rem;
    align-items: center;
    flex-wrap: wrap;
    row-gap: 0.25rem;
}

/* Просмотры и звёзды в мета-строке шли голыми числами: «agniy · 30.08.2026
   23:14 · 1543 · 0» не читается вообще. Значки ставим из CSS, а не в
   разметку: title у этих <span> уже есть, а лишний текстовый узел ушёл бы
   и в поиск по странице, и в выдачу скринридера дублем к нему. */
.snippet-card-views::before,
.snippet-views::before {
    content: "👁";
    margin-right: 0.25rem;
    opacity: 0.75;
}
.snippet-card-stars::before,
.snippet-stars::before {
    content: "★";
    margin-right: 0.25rem;
    opacity: 0.75;
}

.snippet-card-tags {
    margin-top: 0.5rem;
    flex-wrap: wrap;
    row-gap: 0.375rem;
}
.snippet-card-tags .badge,
.my-snippets-tags .badge,
.snippet-tags .badge {
    text-decoration: none;
}
.snippet-card-tags .badge:hover,
.my-snippets-tags .badge:hover {
    color: var(--primary-foreground);
    background-color: var(--primary-surface);
}

/* Превью кода в карточке: вьюха отдаёт первые ~10 строк, но строки бывают
   длинными, а карточек на странице двадцать. Высоту ограничиваем, чтобы
   витрина оставалась просматриваемой, а не превращалась в ленту простыней. */
.snippet-card-code {
    margin-top: 0.75rem;
    max-height: 15rem;
}


/* =========================================================================
 * 3. МОИ СНИППЕТЫ — codskl/my_snippets.html
 * ========================================================================= */

/* Строка своего сниппета: название, статус видимости, мета справа, теги
   отдельной строкой снизу. Карточка компактнее галерейной — здесь нет
   превью кода, и список читают как реестр. */
.my-snippets-item {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem 0.625rem;
    padding: 0.875rem 1rem;
    background-color: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}
.my-snippets-item:hover {
    border-color: var(--border-interactive);
}

.my-snippets-link {
    font-size: 1.0625rem;
    font-weight: 600;
    color: var(--foreground);
    text-decoration: none;
}
.my-snippets-link:hover {
    color: var(--primary);
    text-decoration: underline;
}

/* Мета уходит вправо: у строк разной длины даты выстраиваются в столбец и
   список читается как таблица. На узком экране auto-margin схлопывается
   переносом (см. адаптив в конце файла). */
.my-snippets-meta {
    margin-left: auto;
    font-size: 0.875rem;
    text-align: right;
}

/* Теги — своей строкой во всю ширину карточки: они относятся к сниппету
   целиком, а не к дате, рядом с которой оказались бы в общем потоке. */
.my-snippets-tags {
    flex: 1 0 100%;
    flex-wrap: wrap;
    row-gap: 0.375rem;
    margin-top: 0.125rem;
}


/* =========================================================================
 * 4. КОРЗИНА — codskl/trash.html
 * ========================================================================= */

.snippets-trash-note {
    max-width: 70ch;
    margin-top: 1rem;
}

.snippets-trash-item {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem 0.625rem;
    padding: 0.875rem 1rem;
    background-color: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

/* Удалённый сниппет — не ссылка (страницы у него уже нет), поэтому имя
   набрано весом, а не цветом ссылки: синий текст без перехода обманывает. */
.snippets-trash-name {
    font-size: 1.0625rem;
    font-weight: 600;
}

.snippets-trash-meta {
    font-size: 0.875rem;
}

/* Кнопки прижаты к правому краю строки. Форма восстановления — inline-блок:
   по умолчанию <form> занимает всю ширину и роняла бы «Удалить навсегда»
   на следующую строку. */
.snippets-trash-actions {
    margin-left: auto;
    flex-wrap: wrap;
    row-gap: 0.375rem;
}
.snippets-trash-actions form {
    margin: 0;
}


/* =========================================================================
 * 5. СТРАНИЦА СНИППЕТА — codskl/snippet_detail.html
 * ========================================================================= */

.snippet-detail {
    margin-top: 1.5rem;
}

/* Шапка сниппета — карточка: заголовок, описание, мета, теги и действия
   визуально отделены от кода, который идёт следом собственным блоком. */
.snippet-header {
    padding: 1.25rem;
    background-color: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

/* `space-y-8` в разметке даёт соседям внутри шапки 2rem — для плотного
   блока «описание, мета, теги» это разрыв, а не ритм. */
.snippet-header.space-y-8 > * + * {
    margin-top: 0.75rem;
}

.snippet-title {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.625rem;
    font-size: 1.75rem;
    font-weight: 700;
    line-height: 1.25;
}

.snippet-description {
    max-width: 70ch;
    font-size: 1rem;
}

.snippet-meta {
    align-items: center;
    flex-wrap: wrap;
    row-gap: 0.25rem;
    font-size: 0.875rem;
}

.snippet-fork-note {
    font-size: 0.875rem;
}

.snippet-tags {
    flex-wrap: wrap;
    row-gap: 0.375rem;
}

/* Ряд действий отделён линией: правка, удаление и форк — не часть описания.
   Формы внутри ряда сбрасываем в inline, иначе каждая кнопка встала бы на
   свою строку. */
.snippet-actions {
    flex-wrap: wrap;
    row-gap: 0.5rem;
    padding-top: 0.875rem;
    border-top: 1px solid var(--border);
}
.snippet-actions form {
    margin: 0;
}

/* Панель над кодом прижата к правому краю и «приклеена» к блоку кода:
   отрицательного отступа нет, просто общий вертикальный ритм задаётся
   здесь, а не собственными margin у <pre>. */
.snippet-code-toolbar {
    justify-content: flex-end;
    flex-wrap: wrap;
    row-gap: 0.5rem;
    margin-top: 1.25rem;
    margin-bottom: 0.5rem;
}

.snippet-code {
    margin-bottom: 1.5rem;
}

/* Полный код сниппета высотой не ограничен намеренно: страница существует
   ровно для того, чтобы его прочитать целиком. Ограничение стоит только у
   превью в карточке витрины. */
.snippet-code pre {
    font-size: 0.875rem;
}

/* --- Нумерация строк (плагин line-numbers) ------------------------------- */

/* Мост под вендорный CSS плагина. Колонку номеров плагин рисует правилами
   `.line-numbers .line-numbers-rows` — они матчатся и у нас. А вот геометрию
   контейнера он вешает на `pre[class*="language-"].line-numbers`, и этого
   селектора наша разметка не даёт: класс языка стоит на <code>, а не на
   <pre> (каноническая разметка Prism), поэтому без этих трёх свойств номера
   встают absolute относительно неподходящего предка и уезжают за пределы
   блока.

   Дописать `language-…` второму элементу — <pre> — было бы короче, но тогда
   на блок начали бы действовать правила самой темы
   (`pre[class*=language-]{padding:1em;margin:.5em 0}` из
   css/vendor/prism.min.css). Тема подключена в base.html ПОСЛЕ
   {% block extra_css %}, специфичность у неё та же, что у `.snippet-code pre`
   (0,1,1) — и она перебила бы отступы блока кода, объявленные выше в этом
   файле. Мост дешевле и не трогает остальные страницы. */
.snippet-code pre.line-numbers {
    position: relative;
    padding-left: 3.8em;
    counter-reset: linenumber;
}
.snippet-code pre.line-numbers > code {
    position: relative;
    white-space: inherit;
}

/* Цвета нумерации. В вендорном файле и разделитель, и сами номера
   захардкожены как #999: на светлой теме это блёкло, на тёмной — почти
   сливается с подложкой. Берём те же роли, что 04-prose.css берёт для кода
   внутри статьи: рамка блока и приглушённый текст, оба уже привязаны к теме. */
.snippet-code .line-numbers-rows {
    border-right-color: var(--border);
}
.snippet-code .line-numbers-rows > span::before {
    color: var(--muted-foreground);
}


/* =========================================================================
 * 6. ФОРМА СНИППЕТА — codskl/snippet_form.html
 * ========================================================================= */

/* Форма — колонка чтения, а не вся ширина контента: поле названия шириной
   в 900px выглядит как ошибка вёрстки. Поле кода из этого ограничения
   выходит (см. ниже) — ему ширина нужна. */
.snippet-form-page {
    margin-top: 1.5rem;
}

.snippet-form-title {
    font-size: 2rem;
    font-weight: 700;
    line-height: 1.2;
    margin-bottom: 1.25rem;
}

.snippet-form {
    padding: 1.25rem;
    background-color: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

/* Голый <textarea name="code"> (деградация без JS) и редактор CodeMirror,
   который навешивается поверх него, должны выглядеть одним и тем же полем:
   один моноширинный шрифт, одна рамка, один радиус. */
.snippet-form textarea[name="code"] {
    font-family: var(--font-mono);
    font-size: 0.875rem;
    line-height: 1.6;
    resize: vertical;
}
.snippet-form .CodeMirror {
    height: auto;
    font-family: var(--font-mono);
    font-size: 0.875rem;
    line-height: 1.6;
    border: 1px solid var(--border-interactive);
    border-radius: 0.5rem;
}
/* Минимальная высота задаётся прокручиваемой области, а не внешнему .CodeMirror.
   При `height: auto` редактор тянется по содержимому, и min-height на внешнем
   боксе растягивал только его: под коротким кодом оставалась пустая полоса
   чужого фона, потому что .CodeMirror-scroll сохранял высоту содержимого. */
.snippet-form .CodeMirror-scroll {
    min-height: 22rem;
}

.snippet-form textarea[name="description"] {
    min-height: 5rem;
    resize: vertical;
}

.snippet-form-actions,
.snippet-form .form-actions {
    flex-wrap: wrap;
    row-gap: 0.5rem;
    padding-top: 1rem;
    border-top: 1px solid var(--border);
}


/* =========================================================================
 * 7. ПОДТВЕРЖДЕНИЯ УДАЛЕНИЯ — snippet_confirm_delete.html / _purge.html
 * ========================================================================= */

/* Обе страницы — один вопрос и два действия. Узкая колонка здесь работает
   на смысл: решение принимают, прочитав предупреждение целиком. */
.snippet-delete-page,
.snippet-purge-page {
    max-width: 44rem;
    margin-top: 2rem;
    padding: 1.5rem;
    background-color: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

.snippet-delete-title,
.snippet-purge-title {
    font-size: 1.5rem;
    font-weight: 700;
    line-height: 1.3;
    margin-bottom: 0.75rem;
}

.snippet-delete-warning,
.snippet-purge-warning {
    color: var(--muted-foreground);
    margin-bottom: 1.25rem;
}

/* Безвозвратное удаление отличается от отправки в корзину не только
   подписью кнопки: страница очистки красит предупреждение опасным цветом,
   потому что отменить это действие уже нечем. */
.snippet-purge-warning {
    color: var(--destructive-text);
}


/* =========================================================================
 * 8. АДАПТИВ
 *
 * Брейкпоинт 768px — тот же, что у pages/blog.css и pages/tutorials.css.
 * ========================================================================= */

@media (max-width: 768px) {
    /* На узком экране мета в строке своего сниппета больше не прижимается
       вправо: auto-margin оставлял её висеть в одиночестве на пустой
       строке под названием. */
    .my-snippets-meta,
    .snippets-trash-actions {
        margin-left: 0;
        text-align: left;
    }

    /* Панель фильтров в одну колонку: пять полей по 10rem на 390px дают
       два поля в ряд с полем поиска враспор — читается хуже, чем список. */
    .snippet-filters label,
    .snippet-filters label:has(input[type="search"]) {
        flex: 1 0 100%;
        max-width: none;
    }
    .snippet-filters .btn {
        flex: 1 0 auto;
    }

    .snippet-gallery-title,
    .my-snippets-title,
    .snippets-trash-title,
    .snippet-form-title {
        font-size: 1.625rem;
    }

    .snippet-title {
        font-size: 1.375rem;
    }

    .snippet-header,
    .snippet-form,
    .snippet-delete-page,
    .snippet-purge-page {
        padding: 1rem;
    }

    /* Действия сниппета в столбец: на 390px ряд из «Редактировать»,
       «Удалить» и «Дублировать» переносится по одной кнопке в строку и
       выглядит рваным. */
    .snippet-actions .btn,
    .snippet-actions form,
    .snippet-actions form .btn {
        width: 100%;
    }
}
