/*
 * Оформление интерфейса: одно место на все экраны.
 *
 * Стили общие для каркаса и разделов: экран списка документов ничего не
 * добавляет себе отдельно, потому что следующий экран этапа (оглавление, поиск по
 * заголовкам, группы документов, карта пробелов) — это та же страница с таблицами,
 * списками и признаками. Признаки рисуются одним элементом `.badge`, а вид
 * признака задаётся модификатором по ключу класса из API (`badge--size-heavy`,
 * `badge--sections-partial`): новый класс метрики описывается здесь, а в коде
 * экрана он уже показывается.
 *
 * Шрифты — только системные: страница должна открываться без сети, а значит, без
 * загрузки шрифтов из интернета. Цвета заданы переменными на `:root`, и тёмная
 * схема — это их другой набор: она включается системной настройкой
 * (`prefers-color-scheme`), отдельного переключателя в интерфейсе нет.
 *
 * Оформление сдержанное: различимы состояния и признаки, а движения нет —
 * кроме перехода по ссылкам и нажатий, ничего не шевелится.
 */

:root {
  color-scheme: light dark;

  --bg: #f4f5f7;
  --panel: #ffffff;
  --panel-soft: #fafbfc;
  /* Подложка под блоками списка: плотнее блока и отличима от фона страницы. */
  --panel-under: #eaedf1;
  --ink: #1b1f24;
  --ink-soft: #4b5563;
  --muted: #7b8794;
  --line: #e2e6ea;
  --line-strong: #cdd4db;
  --accent: #2b6cb0;
  --accent-soft: #e8f0f9;

  --blue-bg: #e6eff9;
  --blue-ink: #1f5488;
  --green-bg: #e5f3ea;
  --green-ink: #1f6b40;
  --amber-bg: #fdf1dc;
  --amber-ink: #8a5a12;
  --red-bg: #fbe7e6;
  --red-ink: #9c2f28;
  --violet-bg: #eeeaf9;
  --violet-ink: #4b3d8f;
  --teal-bg: #e2f1f2;
  --teal-ink: #1c6367;
  --gray-bg: #eef0f2;
  --gray-ink: #55606c;
  /* Уголок «не прочитано» в блоке: единственный цвет, несущий смысл (см. `.read-mark`). */
  --mark-unread: #e6a90c;

  --radius: 6px;
  --gap: 16px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #16181c;
    --panel: #1e2126;
    --panel-soft: #23272d;
    --panel-under: #1a1d21;
    --ink: #e7eaee;
    --ink-soft: #b3bcc6;
    --muted: #8b95a1;
    --line: #2c3138;
    --line-strong: #3a4048;
    --accent: #79aee0;
    --accent-soft: #1d2b3a;

    --blue-bg: #1d2b3a;
    --blue-ink: #9cc4e8;
    --green-bg: #1b2f24;
    --green-ink: #90cba9;
    --amber-bg: #33290f;
    --amber-ink: #e0bb78;
    --red-bg: #33201f;
    --red-ink: #e79b95;
    --violet-bg: #272341;
    --violet-ink: #b6abee;
    --teal-bg: #172e30;
    --teal-ink: #8fc8cb;
    --gray-bg: #262a30;
    --gray-ink: #adb6c0;
    /* Тот же уголок в тёмной схеме: приглушённый жёлтый — чистый выжигает глаз. */
    --mark-unread: #c6971f;
  }
}

* {
  box-sizing: border-box;
}

/*
 * Скрытое — скрыто.
 *
 * Экраны и куски экранов прячутся атрибутом `hidden` (`app.js`, `sections/*`), и
 * полагаться при этом на браузер нельзя: своё `display` (у сетки — `grid`, у
 * органов управления — `flex`) перебивает его правило `[hidden]` — оно у
 * браузера слабее любого объявленного. Правило стоит здесь, а не у каждого
 * скрываемого узла: мест, где прячут узел, десятки, и забытое `display` в одном
 * из них — это узел, который виден, хотя его скрыли.
 */
[hidden] {
  display: none !important;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  font-size: 15px;
  line-height: 1.45;
}

/* ---------- Шапка и подвал каркаса ---------- */

.topbar {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
}

.topbar-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--gap);
  max-width: 1180px;
  margin: 0 auto;
  padding: 12px 20px;
}

/*
 * Бренд: имя и подпись под ним, столбиком.
 *
 * Имя стоит в углу шапки, а подпись — под ним, а не рядом: рядом они занимают
 * ширину, которой в строке ещё нужны разделы. Кегль и начертание имени от этого
 * не меняются — подпись лишь переезжает на вторую строку.
 */
.brand {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0;
}

.brand-name {
  font-size: 17px;
  font-weight: 600;
  letter-spacing: 0.01em;
}

.brand-note {
  color: var(--muted);
  font-size: 13px;
}

/* Разделы стоят сразу за брендом, в первой строке шапки: она от этого остаётся
   одной строкой, а не двумя (см. `.brand`). */
.sections {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-left: 0;
}

.section-link {
  display: inline-block;
  padding: 5px 12px;
  border-radius: var(--radius);
  color: var(--ink-soft);
  text-decoration: none;
  font-size: 14px;
}

.section-link:hover {
  background: var(--panel-soft);
  color: var(--ink);
}

.section-link.is-active {
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 600;
}

.page {
  max-width: 1180px;
  margin: 0 auto;
  padding: 20px;
}

.footer {
  max-width: 1180px;
  margin: 0 auto;
  padding: 8px 20px 28px;
  color: var(--muted);
  font-size: 13px;
}

/* ---------- Раздел и его шапка ---------- */

.section-head h2 {
  margin: 4px 0 4px;
  font-size: 20px;
  font-weight: 600;
}

/*
 * Строка заголовка с кнопкой панели инструментов: кнопка стоит рядом с тем, что
 * называет выдачу.
 *
 * Свёрнутая панель ничего не прячет молча: на кнопке словами сказано, чем выдача
 * сужена и по какому периоду разложена (`toolsNote`), и место этой подписи — там
 * же, где назван сам список, а не отдельной строкой под ним. Кнопка переносится
 * под заголовок, когда в строке ей не хватило места: так на узком экране она
 * остаётся целой, а заголовок — читаемым.
 */
.section-head--tools {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 14px;
  margin-bottom: 12px;
}

.section-note {
  margin: 0 0 var(--gap);
  max-width: 78ch;
  color: var(--ink-soft);
  font-size: 14px;
}

.hint {
  margin: 0 0 10px;
  color: var(--muted);
  font-size: 13px;
}

/* ---------- Органы управления ---------- */

.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 8px 10px;
  margin-bottom: 12px;
}

.search {
  flex: 1 1 240px;
  min-width: 180px;
  padding: 7px 10px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
}

.search:focus-visible,
.filter:focus-visible,
.page:focus-visible,
.reset:focus-visible,
.sort-button:focus-visible,
.tools-toggle:focus-visible,
.depth-side:focus-visible,
button.tree-row:focus-visible,
.facts-more > summary:focus-visible,
.doc-link:focus-visible,
.relation-link:focus-visible,
.back-link:focus-visible,
.cluster-head:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.filter-field {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.filter-label {
  color: var(--muted);
  font-size: 12px;
}

.filter,
.page,
.reset {
  padding: 6px 10px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
}

.reset,
.page {
  cursor: pointer;
}

/*
 * Плотная панель списка: полей у неё много, и строка у них одна.
 *
 * Ширину строки поля делят между собой, а не берут её по содержимому: список
 * значений фильтра приходит из базы, и подписи в нём длинные («Учебный материал ·
 * 120»), а полей одиннадцать — по содержимому они разъезжаются на две строки и
 * больше. Сжатое поле показывает выбранное значение не целиком; обрезанное видно
 * в раскрытом списке, а словами отбор назван на кнопке панели (`toolsNote`),
 * поэтому в тесноте обрезается именно оно.
 *
 * Переноса это не отменяет: на узком экране строка всё равно разъедется на
 * несколько — лишь бы страница не поехала вбок.
 */
.toolbar--dense .filter-field {
  flex: 1 1 95px;
  min-width: 0;
}

/* Строка поиска растёт быстрее прочих полей: ширина нужнее ей — по ней набирают
   текст, — а список фильтра и в узком поле открывается целиком. */
.toolbar--dense .search {
  flex: 4 1 170px;
  min-width: 140px;
}

/* Список фильтра заполняет поле целиком: поле решает, сколько ему ширины, а не
   самый длинный пункт списка. */
.toolbar--dense .filter {
  width: 100%;
  min-width: 0;
  padding: 6px 8px;
  font-size: 13px;
}

.reset:hover,
.page:not(:disabled):hover {
  border-color: var(--accent);
  color: var(--accent);
}

.page:disabled {
  color: var(--muted);
  cursor: default;
  opacity: 0.6;
}

/* ---------- Таблица документов ---------- */

.table-wrap {
  overflow-x: auto;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

table.books {
  width: 100%;
  border-collapse: collapse;
}

table.books th,
table.books td {
  padding: 9px 12px;
  border-bottom: 1px solid var(--line);
  text-align: left;
  vertical-align: top;
}

table.books thead th {
  background: var(--panel-soft);
  border-bottom: 1px solid var(--line-strong);
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  white-space: nowrap;
}

table.books tbody tr:last-child td {
  border-bottom: 0;
}

table.books tbody tr:hover td {
  background: var(--panel-soft);
}

.sort-button {
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
}

.sort-button[data-direction="asc"]::after {
  content: " ↑";
}

.sort-button[data-direction="desc"]::after {
  content: " ↓";
}

th.is-sorted .sort-button {
  color: var(--accent);
}

.cell-doc {
  min-width: 260px;
}

.doc-name {
  display: block;
  font-weight: 500;
}

.doc-path {
  display: block;
  margin-top: 2px;
  color: var(--muted);
  font-size: 12px;
  word-break: break-word;
}

.cell-kind {
  white-space: nowrap;
}

/*
 * Дата документа: короткая запись вида «21.09.2026» — ширина под неё.
 *
 * Столбец стоит вторым, сразу за документом, и это выбор из двух: таблица шире
 * экрана и потому прокручивается вбок (`.table-wrap`), а столбец, поставленный
 * последним, оказался бы за краем — то есть новый порядок сортировки был бы не
 * виден тому, кто о нём не знает. Второе место при этом и по смыслу первое:
 * «что это» и «когда написано» читаются парой.
 *
 * Дата изменения файла здесь не показывается намеренно: у материала из папки это
 * то же самое число (дата документа — она и есть дата файла), а у вставленного
 * текстом её не бывает вовсе.
 */
.cell-created {
  white-space: nowrap;
  min-width: 96px;
}

.cell-created .date-note {
  display: block;
  color: var(--muted);
  font-size: 12px;
}

.dash {
  color: var(--muted);
}

/* ---------- Список документов: блоки, группы по датам и отметка ---------- */

/*
 * Экран списка занимает всю ширину окна.
 *
 * `.page` держит ширину в 1180 пикселей — это мера для чтения текста и таблиц,
 * — а сетке блоков тесно: при ширине столбца «документ» (260 пикселей, как у
 * прежней таблицы) в неё влезает четыре столбца вместо пяти-шести. Поэтому у
 * экрана списка страница расширяется, а у остальных экранов остаётся прежней:
 * ширина выбирается по тому, какой раздел сейчас показан.
 *
 * Расширение — не через `100vw`: единица ширины окна включает полосу прокрутки, и
 * `100vw` на настольном браузере даёт горизонтальную прокрутку страницы. Здесь
 * снимается предел ширины, а поля остаются — прокрутки не появляется ни при
 * какой полосе.
 */
body:has(.view[data-section="books"]:not([hidden])) .page,
body:has(.view[data-section="books"]:not([hidden])) .footer {
  max-width: none;
}

/* Кнопка панели инструментов: у неё же видно, чем сужена выдача. */
.tools-toggle {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
  padding: 6px 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}

.tools-toggle:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.tools-caret::before {
  content: "▸";
}

.tools-toggle.is-open .tools-caret::before {
  content: "▾";
}

/* Слово на кнопке и подпись отбора — двумя частями, а не одной строкой текста:
   подпись длинная, и по ней видно и режим группировки, и то, чем сужена выдача. */
.tools-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
}

.tools-state {
  font-weight: 600;
}

/* Подпись отбора — не кнопка, а ответ: она идёт тем же цветом, что и подписи. */
.tools-note {
  color: var(--muted);
  font-weight: 400;
}

.tools-toggle:hover .tools-note {
  color: inherit;
}

/*
 * Подложка под группами блоков: плотнее того, что внутри блоков.
 *
 * Блоки списка светлее фона страницы, и без подложки группа не читалась бы
 * группой: подписи периодов висели бы на том же фоне, что и сами блоки, и строки
 * разных дат слились бы в одну сетку. Подложка плотнее блока, а от фона страницы
 * отличается тоном: в светлой схеме она его темнее, в тёмной — светлее. Она одна
 * на весь список, а не своя у каждой группы: группы и есть один список,
 * разрезанный по датам.
 */
.book-groups {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 12px;
  border-radius: var(--radius);
  background: var(--panel-under);
}

.book-group {
  display: flex;
  flex-direction: column;
}

/*
 * Подпись группы: название и число документов, чертой во всю ширину сетки.
 *
 * Черта и разрыв между группами — то, чем видно, где кончается одна группа и
 * начинается другая: без них блоки разных групп читались бы одной сеткой.
 *
 * Строка переносится: у группы понятий в ней стоят ещё и оговорки о неполноте —
 * «название временное», «показано 3 из 14», — и на узком экране они не должны
 * растягивать страницу вбок (то же правило, что у шапки с кнопкой панели).
 */
.group-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
  margin: 0 0 8px;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--line);
  font-size: 14px;
  font-weight: 600;
}

.group-count {
  color: var(--muted);
  font-size: 12px;
  font-weight: 400;
}

/* Оговорки группы: чем неполон её состав и откуда взято число документов. */
.group-note {
  color: var(--muted);
  font-size: 12px;
  font-weight: 400;
}

/* Название группы, сложенное из её тем: пометка стоит рядом с названием и читается
   как «это ещё не имя» — тем же знаком, что и на экране «Темы и группы». */
.group-temporary {
  color: var(--muted);
  font-size: 12px;
  font-style: italic;
  font-weight: 400;
}

/* Строка под сеткой: «показаны первые N групп из M» и добор остальных. */
.group-more,
.cluster-more {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-top: 10px;
}

/*
 * Сетка блоков: столько столбцов, сколько влезает по 260 пикселей.
 *
 * 260 — ширина столбца «документ» в прежней таблице: блок отвечает на тот же
 * вопрос («что это за материал»), и втрое уже его делать нечем — имя материала
 * занимает под себя три строки почти всегда.
 */
.book-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 10px;
}

.book-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--panel);
  cursor: pointer;
}

.book-card:hover {
  background: var(--panel-soft);
  border-color: var(--line-strong);
}

/* Прочитанное отмечено полосой слева: по сетке это видно не читая подписей. */
.book-card--read {
  border-left-color: var(--green-ink);
}

/*
 * Имя материала — единственный текст блока: под него отведены три строки.
 *
 * Три, а не две: имени в две строки не хватает почти всегда, а место, которое
 * занимали подписи под ним (папка, аннотация), теперь показывается по наведению
 * на блок. Отметка «прочитано» стоит уголком поверх блока и этих строк не
 * отбирает (см. `.read-mark`). Обрезка идёт по высоте, а не по знакам: блок
 * тянется по ширине окна, и обрезка по знакам оставляла бы в широком окне пустое
 * место.
 */
.card-name {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-weight: 500;
  word-break: break-word;
}

/*
 * Состояния блока — пометка дубля — стоят строкой под именем.
 *
 * Строкой состояний блока пользуется только пометка дубля: отметка «прочитано»
 * стоит уголком блока (см. `.read-mark`), а больше в блоке нет ничего сверх
 * имени. Строка есть только у того блока, которому есть что сказать, — иначе
 * пустая строка отбирала бы у блока высоту ни за чем.
 */
.card-state {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

/*
 * Отметка «прочитано» — закрашенный уголок блока, лежащий поверх него (см. `readMark`).
 *
 * Уголок, а не строка и не знак в строке: строкой отметка была четвёртой строкой
 * блока, а блок — это три строки имени, и четвёртой в нём быть нечего. Уголок же
 * лежит поверх блока и в его раскладке не участвует вовсе: появление и снятие
 * отметки не сдвигает ни одну строку имени. Нарисован он градиентом по диагонали
 * квадрата — ни картинок, ни внешних файлов: тем же приёмом нарисованы стрелки
 * сортировки и каретка панели.
 *
 * Катет уголка — 18 px, и в имя он не заходит: расстояние считается от его
 * вершины (правый верхний угол поля блока), а первый знак первой строки стоит от
 * той же вершины не ближе 22 px — 12 px влево (правый отступ блока) и 10 px вниз
 * (верхний), — и уголок накрывает только то, что ближе к вершине по диагонали.
 * Замерено в браузере по всем 319 блокам списка: наименьшее расстояние до
 * вершины уголка — 22,2 px, наложения нет ни у одного блока.
 *
 * Состояний два, и различаются они наличием уголка: не прочитанное отмечено
 * закрашенным углом, прочитанное — ничем. Цвет тут не единственный носитель
 * смысла, а подспорье: жёлтый угол видно по сетке боковым зрением, и он же
 * остаётся виден тем, кому оттенки неразличимы — уголок есть или его нет.
 *
 * Жёлтый в светлой схеме и приглушённый в тёмной (`--mark-unread`): чистый
 * жёлтый на тёмной подложке выжигает глаз и спорит с именем, которое рядом.
 *
 * Подпись отметки («Прочитано, дата» / «Не прочитано») остаётся в `title` и
 * `aria-label` узла, и наведение её показывает: щелчок по уголку при этом ведёт
 * туда же, куда щелчок по блоку, — уголок не ссылка и переход не перебивает.
 */
.read-mark {
  position: absolute;
  top: 0;
  right: 0;
  width: 18px;
  height: 18px;
  border-top-right-radius: 5px;
}

/* Не прочитано: угол закрашен — половина квадрата, отрезанная по диагонали. */
.read-mark--no {
  background: linear-gradient(225deg, var(--mark-unread) 50%, transparent 50%);
}

/* Прочитано: уголка нет — отметка снята, а слова о ней остались подписью узла. */

/*
 * Подсказка блока: папка, аннотация и заголовки 1–2 уровня — по наведению.
 *
 * Видна только под курсором (или под фокусом) и появляется поверх сетки, а не
 * раздвигает её: раскрытие блока в потоке двигало бы соседние блоки под курсором
 * и уводило бы из-под него то, за чем он пришёл. Длинный список заголовков
 * прокручивается внутри неё.
 *
 * Ширины блока подсказке мало: заголовки бывают длинные, а в колонку шириной в
 * блок они не влезают — и обрезаются по краю (одной строкой, см. `.card-heads`),
 * так что ширина подсказки тянется по её содержимому (`max-content`), а блоку
 * равна только как наименьшая допустимая (`min-width`). Заголовок при этом
 * расширяет подсказку до предела, а не переносится в узкую колонку — перенос в
 * списке заголовков читается плохо, потому что строки перестают быть строками.
 *
 * Предел ширины — 440 px и место до края окна (`--more-room`, его считает
 * `placeMore` в `sections/books.js`): в сетке положение блока неизвестно, и
 * сколько подсказке осталось места, знает только страница. Пока переменной нет,
 * предел — ширина блока: подсказка, показанная до замера, за край окна не
 * выйдет, а значит, и поперечной прокрутки не даст.
 *
 * Стоит вплотную к блоку, без зазора (`calc(100% - 1px)` — рамки накладываются):
 * через зазор курсор шёл бы по пустому месту, а на нём подсказка гасла бы — ровно
 * тогда, когда за ней и тянутся.
 */
.card-more {
  display: none;
  position: absolute;
  top: calc(100% - 1px);
  left: 0;
  z-index: 5;
  flex-direction: column;
  gap: 4px;
  width: max-content;
  min-width: min(100%, var(--more-room, 100%));
  max-width: min(440px, var(--more-room, 100%));
  max-height: 320px;
  overflow: hidden auto;
  padding: 8px 10px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.16);
  font-size: 12px;
  line-height: 1.35;
}

.book-card:hover .card-more,
.book-card:focus-within .card-more {
  display: flex;
}

/*
 * Строки подсказки не сжимаются, а прокручиваются.
 *
 * Подсказка — столбик с пределом высоты, и без этого правила столбик ужимал бы
 * свои строки под предел: папка и аннотация — самые короткие из них и ужимались
 * бы первыми, вплоть до нулевой высоты. Место в подсказке отдано заголовкам:
 * длинный список уходит в прокрутку, а не вытесняет папку с аннотацией.
 */
.card-more > * {
  flex: 0 0 auto;
}

/* Блок из нижней половины окна открывает подсказку вверх: снизу ей не хватит места. */
.book-card--up .card-more {
  top: auto;
  bottom: calc(100% - 1px);
}

/*
 * Блок у правого края окна раскрывает подсказку влево.
 *
 * Подсказка шире блока, и справа у такого блока места до края окна меньше, чем
 * сама подсказка: раскрытая вправо, она была бы зажата блоком же — ровно там, где
 * заголовки длиннее всего, — или ушла бы за край окна. Отсчёт от правого края
 * блока места ей оставляет втрое больше (см. `placeMore`).
 */
.book-card--more-left .card-more {
  left: auto;
  right: 0;
}

/* Папка материала — то же, что в блоке было видно всегда, а теперь по наведению. */
.card-more-path {
  overflow: hidden;
  color: var(--muted);
  font-size: 11.5px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/*
 * Аннотация в подсказке: смысл материала.
 *
 * Обрезается по высоте в три строки, а не по числу знаков: подсказка тянется по
 * ширине блока, и обрезка по знакам оставляла бы в широком окне пустое место, а в
 * узком — половину строки. Целиком текст виден в подсказке браузера (атрибут
 * `title`), а не здесь: подсказка отвечает «о чём материал», а не «прочитай меня
 * всю».
 */
.card-more-annotation {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  color: var(--ink-soft);
  overflow-wrap: anywhere;
}

.card-more-title {
  color: var(--muted);
  font-size: 11.5px;
}

.card-more-box {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.card-more-empty,
.card-more-loading {
  color: var(--muted);
}

/*
 * Заголовки материала списком: подразделы стоят под своим разделом, а не рядом.
 *
 * Вложенность показана тремя средствами разом, и это не излишество: в длинном
 * списке одним из них она не читается. Полужирный против обычного и тёмный
 * против светлого — различие в весе и цвете; отступ в 16 px — различие в месте;
 * точка на месте отступа — знак уровня. Отступ сам по себе теряется среди
 * отступов подсказки, а цвет различим не всяким зрением, — знак же и место видны
 * всем. Тем же счётом размечены уровни в оглавлении экрана чтения, и в списке,
 * где рядом стоят разделы и подразделы, различие нужно ещё больше.
 *
 * Раздел отделён от предыдущего просветом (`margin-top`): без него подразделы
 * липли бы к чужому разделу сверху, и где чей подраздел, не было бы видно.
 * Подразделы одного раздела просветом не разделяются — они и есть одна ветвь.
 *
 * Строки обрезаются по краю: заголовок целиком виден в подсказке браузера, а в
 * подсказке блока он должен занимать одну строку — иначе список перестаёт
 * читаться как список.
 */
.card-heads {
  margin: 0;
  padding: 0;
  list-style: none;
}

.card-heads-item {
  overflow: hidden;
  color: var(--ink-soft);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.card-heads-item--1 {
  margin-top: 4px;
  color: var(--ink);
  font-weight: 600;
}

.card-heads-item--1:first-child {
  margin-top: 0;
}

.card-heads-item--2 {
  position: relative;
  padding-left: 16px;
}

/* Знак уровня у подраздела: точка на месте отступа — «это часть того, что выше». */
.card-heads-item--2::before {
  content: "";
  position: absolute;
  top: 0.5em;
  left: 5px;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--line-strong);
}

/* Совпадения поиска: одним числом, а какие заголовки — в подсказке. */
.card-matches {
  color: var(--accent);
  font-size: 12px;
}

/*
 * Фрагмент текста, в котором нашлось слово запроса.
 *
 * Одна строка с обрезкой по краю, как у пометки дубля: фрагмент длиннее строки
 * блока, а показать его надо целиком — целиком он и есть в подсказке. Цвет
 * обычный, а не акцентный: акцентом в блоке отмечено совпадение с заголовком
 * (по нему и ведут поиск), а это текст материала, куда находка не указывает, а
 * объясняет её.
 */
.card-snippet {
  overflow: hidden;
  color: var(--ink-soft);
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/*
 * Пометка дубля в блоке: у копии — ссылка на основной материал, у основного —
 * число копий.
 *
 * Ссылка, а не подпись, у копии потому, что с неё уходят за оригиналом; у
 * основного материала копий в списке нет, и вести с его подписи некуда. Цвет
 * фиолетовый — тот же, что у прочего «это про другое место в базе» (кластеры,
 * группы): дубль не признак качества материала, а его место среди других.
 */
.card-duplicate {
  overflow: hidden;
  color: var(--violet-ink);
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Копия помечена полосой и подложкой: в списке «показать дубли» её видно сразу. */
.book-card--copy {
  border-left-color: var(--violet-ink);
  background: var(--panel-soft);
}

/* ---------- Признаки ---------- */

.badge {
  display: inline-block;
  padding: 1px 7px;
  border-radius: 10px;
  background: var(--gray-bg);
  color: var(--gray-ink);
  font-size: 12px;
  white-space: nowrap;
}

.badge--kind-material { background: var(--blue-bg); color: var(--blue-ink); }
.badge--kind-note { background: var(--gray-bg); color: var(--gray-ink); }

.badge--size-short { background: var(--gray-bg); color: var(--gray-ink); }
.badge--size-medium { background: var(--teal-bg); color: var(--teal-ink); }
.badge--size-large { background: var(--blue-bg); color: var(--blue-ink); }
.badge--size-heavy { background: var(--violet-bg); color: var(--violet-ink); }

.badge--detail-insufficient { background: var(--gray-bg); color: var(--gray-ink); }
.badge--detail-overview { background: var(--amber-bg); color: var(--amber-ink); }
.badge--detail-normal { background: var(--teal-bg); color: var(--teal-ink); }
.badge--detail-detailed { background: var(--green-bg); color: var(--green-ink); }

.badge--sections-complete { background: var(--green-bg); color: var(--green-ink); }
.badge--sections-partial { background: var(--amber-bg); color: var(--amber-ink); }
.badge--sections-sparse { background: var(--red-bg); color: var(--red-ink); }
.badge--sections-not_applicable { background: var(--gray-bg); color: var(--gray-ink); }

/*
 * Уровни сложности — по возрастанию подготовки: от зелёного к фиолетовому.
 *
 * Цвет здесь не украшение, а порядок: уровни идут шкалой, и по цвету видно, куда
 * сдвинулся материал, — как у классов детализации и объёма. Названия уровней
 * приходят в ответе рядом с ключом (`app.storage.difficulty`), поэтому цвета
 * заданы по ключам, а не по словам.
 */
.badge--level-beginner { background: var(--green-bg); color: var(--green-ink); }
.badge--level-intermediate { background: var(--teal-bg); color: var(--teal-ink); }
.badge--level-advanced { background: var(--violet-bg); color: var(--violet-ink); }

/* ---------- Темы документа в строке списка ---------- */

.chip {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  margin: 0 4px 3px 0;
  padding: 1px 8px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel-soft);
  font-size: 12.5px;
}

/* ---------- Сообщения на месте таблицы ---------- */

.panel {
  padding: 18px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
}

.panel-text {
  margin: 0;
  max-width: 78ch;
  color: var(--ink-soft);
  font-size: 14px;
}

.panel-text + .panel-text {
  margin-top: 6px;
}

.panel-error {
  color: var(--red-ink);
  font-weight: 500;
}

.panel-link {
  display: inline-block;
  margin-top: 6px;
  color: var(--accent);
  font-size: 14px;
}

.panel-actions {
  margin: 10px 0 0;
}

/* ---------- Экран материала: шапка и раскладка ---------- */

.back-link {
  display: inline-block;
  margin-bottom: 4px;
  color: var(--ink-soft);
  font-size: 13px;
  text-decoration: none;
}

.back-link:hover {
  color: var(--accent);
}

/*
 * Заголовок материала и отметка «прочитано» — одной строкой.
 *
 * Галка стоит рядом с заголовком, а не отдельной строкой под ним: её ставят,
 * закрыв материал, и искать её глазами по экрану не должно быть делом. На узком
 * экране отметка переносится под заголовок (`flex-wrap`) и остаётся той же
 * строкой разметки.
 */
.title-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 14px;
}

.material-title {
  margin: 0 0 4px;
  font-size: 20px;
  font-weight: 600;
  word-break: break-word;
}

.read-toggle {
  display: inline-flex;
  align-items: baseline;
  gap: 7px;
  margin-bottom: 4px;
  padding: 3px 10px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--ink);
  font-size: 13px;
  cursor: pointer;
  user-select: none;
}

.read-toggle:hover {
  border-color: var(--accent);
}

/* Поставленная отметка видна и в самой галке, и в её подложке. */
.read-toggle.is-read {
  border-color: var(--green-ink);
  background: var(--green-bg);
  color: var(--green-ink);
}

.read-toggle.is-pending {
  cursor: progress;
  opacity: 0.6;
}

.read-toggle .read-check {
  accent-color: var(--green-ink);
}

.read-text {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px;
}

.read-note {
  color: var(--muted);
  font-size: 12px;
}

.read-toggle.is-read .read-note {
  color: inherit;
}

.read-note--error {
  color: var(--red-ink);
}

.material-path {
  color: var(--ink-soft);
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: 12.5px;
  word-break: break-word;
}

.section-note > .badge,
.section-note > .dash {
  margin-left: 8px;
}

/*
 * Сведения стоят сбоку от оглавления, а на узком экране — над ним: в разметке
 * они идут первыми, поэтому одного правила порядка хватает на оба случая, и
 * порядок чтения остаётся осмысленным без перестановки узлов.
 */
.material {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 340px;
  gap: 20px;
  align-items: start;
}

.material-contents {
  grid-column: 1;
  grid-row: 1;
  min-width: 0;
}

.material-facts {
  grid-column: 2;
  grid-row: 1;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
}

.block-title {
  margin: 0 0 8px;
  font-size: 15px;
  font-weight: 600;
}

/* ---------- Сведения о материале ---------- */

/*
 * Аннотация материала — смысл, написанный моделью по оглавлению, и подпись о
 * том, чем он получен. Стоит выше метрик: метрики отвечают, как материал
 * устроен, а аннотация — о чём он, и спрашивают сначала об этом. Отделяется от
 * таблицы метрик воздухом, а не линией: линия здесь уже значит «другой блок» у
 * подборки и у раскрывающихся разделов, и третья такая же стёрла бы разницу.
 */
.annotation {
  margin-bottom: 14px;
}

.annotation-text {
  margin: 0;
  font-size: 14px;
  line-height: 1.45;
  color: var(--ink);
}

.annotation-note {
  margin: 6px 0 0;
  color: var(--muted);
  font-size: 12px;
}

/* Устаревшая аннотация: не ошибка, а повод пересчитать — и потому не красная. */
.annotation-stale {
  color: var(--amber-ink);
}

.annotation-empty {
  margin: 0;
  color: var(--muted);
  font-size: 12.5px;
}

/*
 * Дубли материала: группа одинаковых текстов и её основной материал.
 *
 * Стоит в сведениях под аннотацией и не прячется под раскрытие: это не
 * подробность, а ответ на «почему эта тема попадается дважды». Фиолетовый цвет
 * тот же, что у пометки дубля в списке: по нему видно, что оба места об одном.
 * У копии блок обведён полосой слева — так открывший копию видит, что читает
 * повтор, ещё до чтения подписи.
 */
.duplicates {
  margin: 0 0 14px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--panel-soft);
}

.duplicates--copy {
  border-left-color: var(--violet-ink);
}

.duplicates-head {
  margin: 0;
  color: var(--violet-ink);
  font-size: 13.5px;
  line-height: 1.4;
}

.duplicates-note {
  margin: 4px 0 0;
  color: var(--muted);
  font-size: 12px;
}

.duplicates-list {
  margin: 8px 0 0;
  padding: 0;
  list-style: none;
  font-size: 12.5px;
}

.duplicates-item {
  padding: 2px 0;
  overflow-wrap: anywhere;
}

.duplicates-item--main {
  font-weight: 500;
}

/*
 * Пометка дубля в шапке чтения: копия называет материал, который повторяет.
 *
 * Стоит рядом с размерами материала, тем же цветом, что и в списке: читатель
 * видит, что перед ним повтор, и может уйти за оригиналом, не открывая сведений.
 */
.reader-duplicate {
  margin-left: 8px;
  color: var(--violet-ink);
  font-size: 13px;
}

.facts {
  display: grid;
  grid-template-columns: 92px minmax(0, 1fr);
  gap: 6px 10px;
  margin: 0;
}

.fact-label {
  color: var(--muted);
  font-size: 12.5px;
}

.fact-value {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px;
  margin: 0;
  font-size: 14px;
  word-break: break-word;
}

.fact-note {
  color: var(--muted);
  font-size: 12px;
}

/* Пункты предпосылок: строка сведений растёт списком, и список идёт первым —
   подпись о том, чем он составлен, стоит под ним. */
.prereq-list {
  flex: 1 1 100%;
  margin: 0;
  padding-left: 18px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.prereq {
  font-size: 13px;
}

/* Устаревшая оценка: не ошибка, а повод пересчитать — и потому не красная. */
.fact-stale {
  color: var(--amber-ink);
}

.fact-strong {
  font-weight: 500;
}

.fact-missing {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px;
}

.facts-more {
  margin-top: 12px;
  border-top: 1px solid var(--line);
  padding-top: 8px;
}

.facts-more > summary {
  cursor: pointer;
  color: var(--ink-soft);
  font-size: 13px;
}

.facts-more > summary:hover {
  color: var(--accent);
}

.relation-list {
  margin: 8px 0 0;
  padding: 0;
  list-style: none;
}

.relation {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px;
  padding: 5px 0;
  border-top: 1px solid var(--line);
  font-size: 13px;
  word-break: break-word;
}

.relation:first-child {
  border-top: 0;
}

.relation-rest {
  color: var(--muted);
}

.relation-link {
  color: var(--accent);
  text-decoration: none;
}

.relation-link:hover {
  text-decoration: underline;
}

/* Связи отличаются от прочих списков тем, что ведут на другой экран. */
.badge--link-duplicate { background: var(--red-bg); color: var(--red-ink); }
.badge--link-neighbor { background: var(--blue-bg); color: var(--blue-ink); }
.badge--link-adjacent { background: var(--gray-bg); color: var(--gray-ink); }

/* ---------- Жанры разделов ---------- */

/*
 * Двадцати жанрам по своему цвету не придумать — они перестанут различаться.
 * Цвет задан группами: «что это» — синий, «что делать» — зелёный, «контекст и
 * итоги» — бирюзовый, «риски» — янтарный, «справочное» — сиреневый. Название
 * жанра стоит в самом признаке, поэтому цвету достаточно различать группы.
 */
.badge--genre-definition,
.badge--genre-explanation,
.badge--genre-overview,
.badge--genre-theses,
.badge--genre-intro {
  background: var(--blue-bg);
  color: var(--blue-ink);
}

.badge--genre-instruction,
.badge--genre-checklist,
.badge--genre-practice,
.badge--genre-example,
.badge--genre-recommendation,
.badge--genre-by_role {
  background: var(--green-bg);
  color: var(--green-ink);
}

.badge--genre-history,
.badge--genre-comparison,
.badge--genre-forecast,
.badge--genre-summary {
  background: var(--teal-bg);
  color: var(--teal-ink);
}

.badge--genre-limitations,
.badge--genre-mistakes,
.badge--genre-critique {
  background: var(--amber-bg);
  color: var(--amber-ink);
}

.badge--genre-glossary,
.badge--genre-quiz {
  background: var(--violet-bg);
  color: var(--violet-ink);
}

/* ---------- Оглавление: дерево заголовков ---------- */

.tree-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 4px;
  color: var(--ink-soft);
  font-size: 13px;
}

.tree-size {
  flex: 1 1 auto;
  color: var(--ink-soft);
}

.tree-note,
.tree-counter {
  margin: 0 0 2px;
  color: var(--muted);
  font-size: 12.5px;
}

.tree-note:empty,
.tree-counter:empty {
  display: none;
}

.tree-note + .tree-counter {
  margin-top: 2px;
}

/*
 * Регулятор вложенности: две нажимаемые области и подпись между ними.
 *
 * Области стоят по краям одной коробки, как у кнопки-качелей, и разделены с
 * подписью линиями: по ним видно, что нажимать можно с двух сторон, а подпись —
 * что именно сейчас открыто. Ступень стоит между кнопками, а не под ними, —
 * она меняется от нажатия к нажатию, и на одном месте её видно, не переводя
 * взгляд. Шкала под коробкой показывает, сколько ступеней всего: у материала с
 * двумя уровнями заголовков их три, у шестиуровневого — восемь.
 */
.tree-depth {
  display: flex;
  flex: 0 1 auto;
  flex-direction: column;
  gap: 4px;
  max-width: 100%;
  /* Сжиматься коробке разрешено, а не запрещено: подпись ступени длиннее
     строки размеров, и на узком экране она переносится, а не вылезает за край. */
  min-width: 0;
  margin-left: auto;
}

.depth-rocker {
  display: flex;
  align-items: stretch;
  overflow: hidden;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--panel);
}

.depth-side {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  border: 0;
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}

.depth-side:hover:enabled {
  background: var(--accent-soft);
  color: var(--accent);
}

.depth-side:disabled {
  color: var(--muted);
  cursor: default;
}

.depth-sign {
  font-size: 15px;
  line-height: 1;
}

.depth-cap {
  font-size: 12.5px;
}

.depth-readout {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  justify-content: center;
  gap: 1px;
  max-width: 260px;
  min-width: 0;
  padding: 3px 10px;
  border-right: 1px solid var(--line);
  border-left: 1px solid var(--line);
  background: var(--panel-soft);
  text-align: center;
}

.depth-position {
  color: var(--muted);
  font-size: 11px;
}

.depth-what {
  color: var(--ink);
  font-size: 12.5px;
}

.depth-scale {
  display: flex;
  gap: 4px;
}

.depth-pip {
  flex: 1 1 auto;
  height: 3px;
  border-radius: 2px;
  background: var(--line-strong);
}

.depth-pip--here {
  background: var(--accent);
}

/*
 * На узком экране у кнопок остаются одни знаки: слова «мельче» и «глубже»
 * занимали бы больше места, чем подпись ступени, ради которой всё и стоит.
 * Знаки подписаны подсказкой и доступным именем (`depthRocker`), и по ним видно
 * то же самое — куда ведёт нажатие.
 */
@media (max-width: 560px) {
  .depth-cap {
    display: none;
  }
}

.tree-wrap {
  overflow-x: auto;
  padding: 6px 10px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
}

.tree,
.tree-children {
  margin: 0;
  padding: 0;
  list-style: none;
}

.tree-children[hidden] {
  display: none;
}

/*
 * Строка дерева переносится, а не сжимает название: уровень, номер и название
 * стоят первой строкой, признак жанра и строка файла уходят на вторую, если не
 * помещаются. Без переноса на узком экране названию оставалось бы по букве на
 * строку — оно самое длинное в строке, но самое нужное.
 */
.tree-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 8px;
  width: 100%;
  margin: 0;
  padding: 4px 6px 4px 8px;
  border: 0;
  border-radius: 4px;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
}

button.tree-row {
  cursor: pointer;
}

button.tree-row:hover {
  background: var(--panel-soft);
}

.tree-row[data-depth="1"] { padding-left: 28px; }
.tree-row[data-depth="2"] { padding-left: 48px; }
.tree-row[data-depth="3"] { padding-left: 68px; }
.tree-row[data-depth="4"] { padding-left: 88px; }
.tree-row[data-depth="5"] { padding-left: 108px; }

.tree-marker {
  flex: 0 0 12px;
  color: var(--muted);
  font-size: 11px;
}

.tree-level {
  flex: 0 0 auto;
  min-width: 20px;
  color: var(--muted);
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: 11px;
}

/* Ширина номера закреплена: у раздела без номера клетка пуста, но место держит —
   иначе текст соседних строк стоял бы на разной высоте от края. */
.tree-number {
  flex: 0 0 auto;
  min-width: 34px;
  color: var(--accent);
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: 12.5px;
}

.tree-text {
  flex: 1 1 200px;
  min-width: 0;
  word-break: break-word;
}

.tree-row[data-kind="part"] .tree-text {
  font-weight: 600;
}

/* Номер строки держится правого края: на широком экране его туда уводит
   растянутое название, на узком — этот отступ, когда строка перенеслась. */
.tree-line {
  flex: 0 0 auto;
  margin-left: auto;
  color: var(--muted);
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: 11.5px;
}

.tree-row .badge {
  flex: 0 0 auto;
}

/*
 * Текст раздела под его строкой.
 *
 * Отступ слева — тот же шаг, что у уровней дерева (20px на уровень), и текст
 * стоит под текстом своего заголовка: по левому краю видно, какому разделу он
 * принадлежит, даже когда над ним раскрыты соседние ветви. Полоса слева — та же
 * мысль без отступа: она тянется от строки заголовка вниз, до конца текста.
 */
.tree-section {
  margin: 2px 0 8px 20px;
  padding: 8px 12px;
  border-left: 2px solid var(--line-strong);
  border-radius: 0 4px 4px 0;
  background: var(--panel-soft);
}

.tree-section-note {
  margin: 0;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.5;
}

.tree-section-error {
  color: var(--red-ink);
}

.tree-section-text > :first-child {
  margin-top: 0;
}

.tree-section-text > :last-child {
  margin-bottom: 0;
}

.tree-section-more {
  margin: 8px 0 0;
  padding: 3px 10px;
  border: 1px solid var(--line-strong);
  border-radius: 4px;
  background: var(--panel);
  color: var(--accent);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}

.tree-section-more:hover {
  background: var(--accent-soft);
}

/* ---------- Текст раздела: разметка Markdown ---------- */

/*
 * Оформление текста раздела (`scripts/markdown.js`): то, что в учебнике написано
 * разметкой, читается как текст. Ширина строки ограничена: абзац в тысячу
 * знаков на всю ширину окна читается хуже, чем тот же абзац в семьдесят знаков.
 */
.tree-section-text {
  max-width: 78ch;
  color: var(--ink-soft);
  font-size: 14px;
  line-height: 1.6;
}

.md-paragraph,
.md-item-text {
  margin: 0 0 10px;
}

/*
 * Заголовок внутри текста: он есть у материала, где заголовок остался в теле
 * раздела (`### Таблица`), — в дереве оглавления такие не попадают. Размеры
 * умеренные: заголовок здесь размечает текст, а не открывает экран.
 */
.md-heading {
  margin: 18px 0 8px;
  font-size: 16px;
  font-weight: 600;
  color: var(--ink);
}

.md-heading--1 {
  font-size: 19px;
}

.md-heading--2 {
  font-size: 17px;
}

/* Первый заголовок текста не отбивается от его начала воздухом вдвое. */
.md-heading:first-child {
  margin-top: 0;
}

.md-list {
  margin: 0 0 10px;
  padding-left: 22px;
}

.md-item {
  margin: 0 0 4px;
}

.md-check {
  display: inline-block;
  min-width: 14px;
  margin-right: 4px;
  color: var(--muted);
  font-size: 12px;
}

.md-check--done {
  color: var(--green-ink);
}

.md-code {
  margin: 0 0 10px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--panel);
  overflow-x: auto;
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: 12.5px;
  line-height: 1.5;
}

/*
 * Имя языка блока кода — подписью в углу, а не строкой внутри: строкой оно
 * стало бы частью кода, который копируют. Подпись берётся из имени языка,
 * которое разбор кладёт атрибутом (`markdown.js`), и в тексте блока её нет.
 */
.md-code[data-language] {
  position: relative;
}

.md-code[data-language]::before {
  content: attr(data-language);
  position: absolute;
  top: 0;
  right: 0;
  padding: 1px 6px;
  border-left: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  border-radius: 0 4px 0 4px;
  background: var(--gray-bg);
  color: var(--muted);
  font-size: 11px;
  line-height: 1.6;
}

.md-inline-code {
  padding: 1px 4px;
  border-radius: 3px;
  background: var(--gray-bg);
  color: var(--ink);
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: 12.5px;
}

/*
 * Таблица прокручивается сама: в базе есть таблицы в пять-шесть столбцов, и на
 * узком экране такая таблица растянула бы вбок всю страницу. Прокрутка внутри
 * обёртки оставляет странице её ширину (то же правило, что у блоков кода).
 */
.md-table-wrap {
  margin: 0 0 10px;
  overflow-x: auto;
}

.md-table {
  margin: 0;
  border-collapse: collapse;
  font-size: 13px;
}

.md-cell {
  padding: 4px 8px;
  border: 1px solid var(--line);
  text-align: left;
  vertical-align: top;
}

/* Выравнивание столбца — как записано в разделителе шапки (`markdown.js`). */
.md-cell--center {
  text-align: center;
}

.md-cell--right {
  text-align: right;
}

.md-table thead .md-cell {
  background: var(--gray-bg);
  font-weight: 600;
}

.md-quote {
  margin: 0 0 10px;
  padding: 2px 0 2px 12px;
  border-left: 3px solid var(--line-strong);
  color: var(--muted);
}

/*
 * Выноска Obsidian (`> [!WARNING]`): та же цитата, но с именем вида. Имя вида
 * стоит надписью, а не текстом внутри: это подпись вида, а не то, что написал
 * автор (см. `markdown.js`). Полоса слева — цветом вида: осторожные выноски
 * видно с первого взгляда, а читаются они так же, как обычная цитата.
 */
.md-quote--callout {
  border-left-color: var(--amber-ink);
}

.md-callout-title {
  display: block;
  margin-bottom: 2px;
  color: var(--amber-ink);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

/*
 * Сноска: определение с номером и ссылка на него из текста. Определение стоит
 * в тексте там, где оно написано (обычно в конце материала списком), а не
 * собирается отдельным блоком: так его видно на своём месте и не приходится
 * сшивать сноски разных разделов.
 */
.md-footnote {
  display: flex;
  gap: 8px;
  margin: 0 0 6px;
  font-size: 13px;
  color: var(--ink-soft);
}

.md-footnote-mark {
  flex: 0 0 auto;
  min-width: 18px;
  color: var(--muted);
}

/*
 * Формула: показывается тем, чем написана, — другим начертанием, а не набором.
 * Набор (LaTeX) требует библиотеки, которую нельзя подключать из сети, поэтому
 * формула читается как запись формулы: так видно и что это формула, и что в ней
 * написано (см. `markdown.js`).
 */
.md-math {
  padding: 0 3px;
  border-radius: 3px;
  background: var(--violet-bg);
  color: var(--violet-ink);
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: 0.95em;
}

.md-math-block {
  margin: 0 0 10px;
  padding: 6px 10px;
  border-left: 3px solid var(--violet-ink);
  border-radius: 0 4px 4px 0;
  background: var(--violet-bg);
  color: var(--violet-ink);
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: 13px;
  white-space: pre-wrap;
  overflow-x: auto;
}

.md-fnref {
  font-size: 11px;
  line-height: 0;
}

.md-fnref .md-link {
  text-decoration: none;
}

.md-rule {
  margin: 12px 0;
  border: 0;
  border-top: 1px solid var(--line);
}

.md-link {
  color: var(--accent);
}

/* Ссылка на материал базы: своя страница, а не чужая — и видно, что своя. */
.md-link--material {
  border-bottom: 1px dotted var(--accent);
  text-decoration: none;
}

.md-wiki {
  color: var(--accent);
  border-bottom: 1px dotted var(--line-strong);
  text-decoration: none;
}

/* Знак источника (`[[3]]`) — не ссылка: вести по нему некуда (`markdown.js`). */
span.md-wiki {
  color: var(--muted);
}

.md-image {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 0 0 10px;
}

/* Вложение, которое приложение не отдаёт: подпись вместо пустого места. */
.md-image-note,
.md-attachment {
  color: var(--muted);
  font-style: italic;
}

.doc-link {
  color: inherit;
  text-decoration: none;
}

.doc-link:hover {
  color: var(--accent);
  text-decoration: underline;
}

table.books tbody tr {
  cursor: pointer;
}

/* ---------- Группы документов ---------- */

.block-title-spaced {
  margin-top: 28px;
}

.cluster-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.cluster {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
}

.cluster-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
  width: 100%;
  padding: 10px 14px;
  border: 0;
  border-radius: var(--radius);
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.cluster-head:hover {
  background: var(--panel-soft);
}

.cluster-marker {
  flex: 0 0 12px;
  color: var(--muted);
  font-size: 11px;
}

.cluster-name {
  font-weight: 600;
  word-break: break-word;
}

.cluster-stats {
  color: var(--ink-soft);
  font-size: 13px;
}

/* Пометка временного названия: группа названа словами состава, а не моделью —
   по этому видно, что имя заменится, когда группу назовут. */

/* Ядро группы — документ, вокруг которого она собралась: путь длинный, поэтому
   он переносится, а не растягивает строку. */
.cluster-temporary {
  color: var(--muted);
  font-size: 12px;
  font-style: italic;
}

.cluster-core {
  flex: 1 1 240px;
  min-width: 0;
  color: var(--muted);
  font-size: 12.5px;
  word-break: break-word;
}

.cluster-body {
  padding: 2px 14px 14px;
  border-top: 1px solid var(--line);
}

.cluster-body .block-title {
  margin-top: 14px;
}

.cluster-body .section-note {
  margin-bottom: 8px;
  font-size: 13px;
}

.cluster-loading {
  margin: 12px 0 0;
  color: var(--muted);
  font-size: 13px;
}

/* Маршрут чтения группы: нумерованный список, потому что номер шага — часть
   ответа, а не украшение строки. */
.route-list {
  margin: 8px 0 0;
  padding-left: 24px;
  display: flex;
  flex-direction: column;
}

.route-step {
  padding: 5px 0;
  border-top: 1px solid var(--line);
  font-size: 13px;
  word-break: break-word;
}

.route-step:first-child {
  border-top: 0;
}

.route-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px;
}

.cell-score,
.cell-mentions,
.cell-number {
  text-align: right;
  white-space: nowrap;
}

/* Признак ядра в составе группы: тот же чип, что у тем документа, но заметнее. */
.chip-core {
  border-color: transparent;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 11.5px;
}

/* ---------- Оговорка к карте пробелов ---------- */

/*
 * Оговорка выглядит оговоркой, а не сноской: цвет взят у предупреждающего
 * признака (amber), а не у подписи. Карту читают как ответ «чего в базе нет» —
 * предупредить об обратном нужно раньше, чем этот ответ прочтут.
 */
.caution {
  margin: 0 0 var(--gap);
  padding: 10px 14px;
  border-left: 3px solid var(--amber-ink);
  border-radius: var(--radius);
  background: var(--amber-bg);
  color: var(--amber-ink);
}

.caution-title {
  margin: 0 0 4px;
  font-size: 14px;
  font-weight: 600;
}

.caution-text {
  margin: 0;
  max-width: 96ch;
  font-size: 13.5px;
}

/* ---------- Что читать дальше ---------- */

/*
 * Подборка стоит сразу под метриками материала, внутри тех же сведений, и строки
 * её — тот же список связей (`.relation`), что и ниже под раскрытием: разница в
 * том, что эта связь на виду, а не спрятана под щелчок.
 */
.recs {
  margin-top: 18px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

.recs .section-note {
  margin-bottom: 0;
  font-size: 12.5px;
}

/* Причина рекомендации: связь по темам или соседство по группе. */
.badge--reason-link { background: var(--blue-bg); color: var(--blue-ink); }
.badge--reason-cluster { background: var(--gray-bg); color: var(--gray-ink); }

/* Ссылка внутри подписи — часть строки, а не отдельное действие. */
.hint-link {
  color: var(--accent);
  text-decoration: none;
}

.hint-link:hover {
  text-decoration: underline;
}

@media (max-width: 960px) {
  .material {
    grid-template-columns: minmax(0, 1fr);
  }

  .material-contents,
  .material-facts {
    grid-column: 1;
  }

  .material-facts {
    grid-row: 1;
  }

  .material-contents {
    grid-row: 2;
  }
}

@media (max-width: 720px) {
  .topbar-inner,
  .page,
  .footer {
    padding-left: 16px;
    padding-right: 16px;
  }

  .sections {
    margin-left: 0;
  }

  .brand-note {
    display: none;
  }
}

/* ---------- Раздел настроек: выбор источника модели ---------- */

.source-list {
  display: grid;
  gap: 10px;
  margin-bottom: var(--gap);
}

.source-card {
  padding: 12px 14px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.source-card.is-chosen {
  border-color: var(--accent);
}

.source-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  cursor: pointer;
}

.source-name {
  font-weight: 600;
}

.source-facts {
  margin: 4px 0 0;
  color: var(--muted);
  font-size: 13px;
}

.source-state {
  margin: 6px 0 0;
  max-width: 78ch;
  color: var(--ink-soft);
  font-size: 13px;
}

.source-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-top: 10px;
}

/* Результат проверки источника: ответ модели или её отказ. */
.source-result {
  margin: 0;
  max-width: 78ch;
  color: var(--green-ink);
  font-size: 13px;
}

.source-result-error {
  color: var(--red-ink);
}

.source-key {
  margin-top: var(--gap);
}

.badge--source-chosen {
  background: var(--accent-soft);
  color: var(--accent);
}

/* ---------- Экран чтения: материал целиком и оглавление сбоку ---------- */

/*
 * Ссылка на чтение — на экране материала, под его именем: способов смотреть
 * материал два, и выбирают между ними сразу (`sections/book.js`).
 */
.material-read {
  margin: 6px 0 0;
}

.read-link {
  color: var(--accent);
  font-size: 14px;
  text-decoration: none;
}

.read-link:hover {
  text-decoration: underline;
}

/*
 * Две колонки: оглавление и текст. Оглавление узкое и закреплено — по нему
 * ходят во время чтения, — а текстовая колонка ограничена по ширине строки:
 * абзац в тысячу знаков на всю ширину окна читается хуже того же абзаца в
 * семьдесят знаков (то же правило, что у текста раздела в дереве).
 */
.reader {
  display: grid;
  grid-template-columns: 288px minmax(0, 1fr);
  gap: 28px;
  align-items: start;
}

.reader-toc {
  position: sticky;
  top: 72px;
  max-height: calc(100vh - 96px);
  overflow-y: auto;
  padding-right: 10px;
  border-right: 1px solid var(--line);
}

/*
 * Подпись списка: краска мягче полного накала (`--ink-soft`) — тем набран
 * верхний уровень заголовков, — и с полным накалом подпись читалась бы первой
 * строкой списка, а не подписью к нему. Вес и кегль у неё свои и прежние: она
 * ещё и раскрывающийся орган управления.
 */
.reader-toc-summary {
  margin-bottom: 6px;
  color: var(--ink-soft);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}

.reader-toc-list {
  display: block;
}

/*
 * Строка оглавления: отступ — по уровню заголовка, а не по числу решёток.
 *
 * Уровни идут в списке вперемешку (в базе чаще всего второй и третий), и одного
 * отступа для них мало: строки стоят подряд, и по нему одному уровни сливаются.
 * Признаков уровня поэтому четыре — вес, кегль, цвет и отступ, — и соседние
 * уровни расходятся не меньше чем по двум: у первого (заголовок материала)
 * полный накал краски и самая крупная строка, у второго мягкая краска и
 * полужирный, у третьего та же краска без жирности и кеглем меньше, у самого
 * глубокого — приглушённая краска. Краски взяты из переменных темы: тёмная схема
 * задаёт те же имена другим набором, и второго способа покрасить строку здесь нет.
 */
.reader-item {
  display: block;
  padding: 2px 8px;
  border-left: 2px solid transparent;
  color: var(--ink-soft);
  font-size: 13px;
  line-height: 1.4;
  text-decoration: none;
}

/* Заголовок: полный накал краски и самый крупный кегль — верхний уровень. */
.reader-item[data-level="1"] {
  color: var(--ink);
  font-size: 14px;
  font-weight: 700;
}

/* Подзаголовок: мягкая краска (цвет строки из `.reader-item`) и полужирный. */
.reader-item[data-level="2"] {
  padding-left: 18px;
  font-weight: 600;
}

/* Третий уровень: та же краска, но без жирности и кеглем меньше. */
.reader-item[data-level="3"] {
  padding-left: 28px;
  font-size: 12.5px;
}

/*
 * Самый глубокий уровень: приглушённая краска — тот же тон, каким в интерфейсе
 * подписаны второстепенные места. От третьего уровня его отделяют цвет, кегль и
 * отступ: вглубь оглавление уходит редко (четвёртый уровень — 34 строки на всю
 * базу), и там строка и должна читаться тише прочих.
 */
.reader-item[data-level="4"] {
  padding-left: 38px;
  color: var(--muted);
  font-size: 12px;
}

.reader-item[data-level="5"],
.reader-item[data-level="6"] {
  padding-left: 48px;
  color: var(--muted);
  font-size: 12px;
}

/*
 * Длинный заголовок переносится, а не выезжает за колонку: в базе есть заголовок
 * с именем файла без пробелов (`excerpt_from_previous_claude_message.txt`), а
 * колонка оглавления — 288 px: без переноса такое слово уводило бы вбок весь
 * список. Тем же правилом переносится название раздела в дереве (`.tree-text`).
 */
.reader-item-text {
  word-break: break-word;
}

.reader-item:hover {
  background: var(--panel-soft);
  color: var(--ink);
}

/*
 * Текущий раздел: подсветка сильнее уровня — цветом акцента, а не краской
 * уровня, и видна на любой глубине.
 */
.reader-item.is-current {
  border-left-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 600;
}

/* Строка заголовка жирнее и под подсветкой: её вес выше того, что даёт она. */
.reader-item.is-current[data-level="1"] {
  font-weight: 700;
}

.reader-body {
  min-width: 0;
  max-width: 76ch;
}

/*
 * Раздел чтения: заголовок и текст. Отступ сверху нужен затем, что закреплённая
 * шапка приложения закрывает верх окна, и переход по оглавлению без него
 * останавливался бы на строке, спрятанной под шапкой (`scroll-margin-top`).
 */
.reader-section {
  margin: 0 0 24px;
  scroll-margin-top: 84px;
}

.reader-heading {
  margin: 20px 0 10px;
  font-size: 20px;
  font-weight: 600;
  line-height: 1.3;
  scroll-margin-top: 84px;
}

.reader-heading--1 {
  font-size: 23px;
}

.reader-heading--2 {
  font-size: 20px;
}

.reader-heading--3 {
  font-size: 17px;
}

.reader-heading--4,
.reader-heading--5,
.reader-heading--6 {
  font-size: 15px;
}

/* Текущий раздел отмечается и в тексте — чертой слева, а не цветом заголовка. */
.reader-section.is-current > .reader-heading {
  padding-left: 10px;
  border-left: 3px solid var(--accent);
  margin-left: -13px;
}

.reader-text {
  color: var(--ink-soft);
  font-size: 15px;
  line-height: 1.65;
}

/*
 * Начало материала — текст без заголовка, который идёт перед первым разделом.
 * Отделён от разделов чертой снизу: у него нет заголовка, и без этой черты он
 * слился бы с первым разделом в один кусок текста (у материала без заголовков
 * вовсе черты нет — за ней ничего не следует).
 */
.reader-preamble {
  padding-bottom: 16px;
  border-bottom: 1px solid var(--line);
}

.reader-preamble:only-child {
  padding-bottom: 0;
  border-bottom: none;
}

.reader-more {
  margin: 0 0 16px;
  color: var(--muted);
  font-size: 13px;
}

/* Прежнее место заголовка в дереве — не блок: список читается строками. */
@media (max-width: 960px) {
  .reader {
    grid-template-columns: minmax(0, 1fr);
    gap: 16px;
  }

  .reader-toc {
    position: static;
    max-height: none;
    padding-right: 0;
    border-right: 0;
  }

  .reader-body {
    max-width: 100%;
  }

  .reader-section,
  .reader-heading {
    scroll-margin-top: 76px;
  }
}

/* ---------- Добавление учебника вставкой текста ---------- */

/* Поле для текста: моноширинный шрифт и своя высота — в учебнике важны отступы
   и решётки заголовков, и по ним видно, что вставлено то, что нужно. Поле
   растягивается по ширине раздела и не растёт само: текст в 154 КБ в поле,
   растущем по содержимому, вытеснил бы кнопку за экран. */
.paste-field {
  display: block;
  margin-bottom: 8px;
}

.paste-text {
  width: 100%;
  max-width: 100%;
  min-height: 320px;
  max-height: 60vh;
  padding: 12px 14px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--ink);
  font-family: ui-monospace, "Cascadia Mono", "Consolas", monospace;
  font-size: 13.5px;
  line-height: 1.6;
  resize: vertical;
}

.paste-text:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.paste-counter {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 0 12px;
  color: var(--muted);
  font-size: 13px;
}

.paste-size {
  color: var(--ink-soft);
}

.paste-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 10px 16px;
  margin-bottom: 14px;
}

.paste-submit {
  padding: 9px 18px;
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  background: var(--accent);
  color: #ffffff;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
}

.paste-submit:hover:not(:disabled) {
  filter: brightness(1.08);
}

.paste-submit:disabled {
  border-color: var(--line-strong);
  background: var(--gray-bg);
  color: var(--gray-ink);
  cursor: progress;
}

.paste-submit:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.paste-note {
  margin: 0;
  max-width: 78ch;
  color: var(--muted);
  font-size: 12.5px;
}

.paste-error,
.paste-result {
  margin-top: 14px;
  padding: 16px 18px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
}

.paste-error {
  border-color: var(--red-ink);
}

.paste-result .block-title {
  margin-top: 0;
}

.paste-links {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  margin: 10px 0 4px;
}

.paste-links .doc-link {
  color: var(--accent);
}

.facts-paste {
  margin-top: 12px;
}

.panel-warning {
  margin-top: 12px;
  border-color: var(--amber-ink);
  background: var(--amber-bg);
  color: var(--amber-ink);
}

.panel-warning .panel-text {
  color: var(--amber-ink);
}

.warning-list {
  margin: 6px 0;
  padding-left: 20px;
  font-size: 14px;
}

/* ---------- Метаданные документа в карточке ---------- */

.metadata {
  margin: 14px 0;
  padding: 12px 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.metadata .block-title {
  margin-bottom: 6px;
}

.facts-metadata {
  margin-top: 8px;
}

/* Список значения поля: элементы идут строками, а не чипами, — так они читаются
   как список, каким их и написали. */
.field-list {
  margin: 0;
  padding-left: 18px;
  font-size: 14px;
}

/* Сложное значение показано текстом, как записано: свёрнутый в строку YAML
   читать нельзя, а он и есть значение поля. */
.field-verbatim {
  margin: 0;
  padding: 8px 10px;
  max-width: 100%;
  overflow-x: auto;
  border-radius: var(--radius);
  background: var(--panel-soft);
  color: var(--ink-soft);
  font-family: ui-monospace, "Cascadia Mono", "Consolas", monospace;
  font-size: 12.5px;
  line-height: 1.5;
  white-space: pre-wrap;
}
