/* ═══ tokens.css ═══ */
/* Токены оформления руководства пользователя — ЕДИНЫЙ ИСТОЧНИК значений для двух
   артефактов, которые человек видит подряд: веб-страница руководства на портале
   (#204) и PDF, который он же отправляет коллеге (#203). Задача — #358.

   ЗДЕСЬ И ТОЛЬКО ЗДЕСЬ пишутся литералы цвета. В blocks.css / web.css /
   print.css цвет берётся через var(): правило то же, что в кабинете
   (`keyportal/oso_keyportal/templates/tokens.css`, сторож
   `test_brand_colours_are_not_written_by_hand_in_the_stylesheets`), и по той же
   причине — иначе «поправить палитру» превращается в поиск шестнадцатеричных
   значений по каталогу, и один артефакт остаётся старым.

   Палитра — не новая, а копия кабинета: полярная ночь, северное сияние, белый
   силуэт на холодном фоне. Кабинет и руководство открывают за один заход, они
   обязаны выглядеть одним продуктом. Расхождение копий сторожит именованный
   тест (`test_manual_palette_is_the_cabinet_palette`).

   Внешних шрифтов и CDN здесь нет и не будет: периметр закрыт, страница обязана
   открываться без сети (`test_manual_page_loads_without_the_network`). */

:root {
  /* ═══ Копия палитры кабинета ═══════════════════════════════════════════
     Значения ниже обязаны совпадать с keyportal/oso_keyportal/templates/
     tokens.css байт в байт по смыслу (сравнение — с нормализацией пробелов). */

  /* --- Полярная ночь: фон (linearGradient #night) --------------------- */
  --oso-night-0: #071b3a;
  --oso-night-1: #0b2f5e;
  --oso-night-2: #123f6f;

  /* --- Северное сияние: акценты (linearGradient #aurora) -------------- */
  --oso-aurora-teal: #38f0c0;
  --oso-aurora-blue: #4aa8ff;
  --oso-aurora-violet: #a56bff;

  /* --- Мех и лёд ------------------------------------------------------ */
  --oso-fur: #ffffff;
  --oso-fur-shade: #cfe6f5;
  --oso-ice: #cfefff;
  --oso-ice-deep: #8fb6cf;

  /* --- Нейро-узор на лбу: цвет НАСТОЯЩИХ кнопок продукта -------------- */
  --oso-node: #2bb3ff;

  /* --- Чернила: глаза и нос знака ------------------------------------ */
  --oso-ink: #0d2136;

  /* --- Тревога: единственный тёплый цвет в холодной палитре ----------- */
  --oso-alert: #ff9a8f;

  /* --- Производные роли ---------------------------------------------- */
  --oso-bg: linear-gradient(160deg, var(--oso-night-0) 0%,
                            var(--oso-night-1) 58%, var(--oso-night-2) 100%);
  --oso-bg-aurora: radial-gradient(120% 60% at 12% 0%,
                     rgba(56, 240, 192, .30) 0%, rgba(56, 240, 192, 0) 55%),
                   radial-gradient(110% 55% at 88% 6%,
                     rgba(165, 107, 255, .28) 0%, rgba(165, 107, 255, 0) 60%),
                   radial-gradient(90% 45% at 50% 0%,
                     rgba(74, 168, 255, .26) 0%, rgba(74, 168, 255, 0) 60%);

  --oso-surface: rgba(255, 255, 255, .07);
  --oso-surface-strong: rgba(255, 255, 255, .12);
  --oso-border: rgba(207, 239, 255, .18);
  --oso-border-strong: rgba(207, 239, 255, .34);

  --oso-text: var(--oso-fur);
  --oso-text-dim: rgba(207, 239, 255, .74);
  --oso-link: var(--oso-node);

  --oso-radius: 18px;
  --oso-radius-sm: 10px;

  /* Стек шрифта — системный: файла гарнитуры в периметре может не быть вовсе,
     а тянуть её из сети запрещено. От кабинета отличается ровно одним именем —
     «DejaVu Sans» перед `sans-serif`: WeasyPrint про `system-ui` не знает и без
     явного имени уехал бы на «шрифт по умолчанию образа», то есть на кириллицу
     без гарантий. Это единственное расхождение с копией палитры, и оно названо
     в сообщении сторожа. */
  --oso-font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
              Arial, "DejaVu Sans", sans-serif;
  --oso-shadow: 0 18px 48px rgba(7, 27, 58, .55);
  --oso-focus: 0 0 0 3px rgba(43, 179, 255, .55);

  /* ═══ Что добавлено руководством ═══════════════════════════════════════
     Всё, что ниже, — РОЛИ, а не цвета. Компоненты (blocks.css) красятся только
     ролями, а print.css переопределяет роли и не трогает палитру знака выше.
     Разделение не косметическое: метки на кадрах обязаны остаться теми же в
     обоих артефактах (см. --mark-* ниже), и если бы лист перекрашивал сами
     --oso-aurora-*, метки уехали бы вместе с ними. */

  /* Моноширинный компаньон: команды и ключи. «DejaVu Sans Mono» — по той же
     причине, что и «DejaVu Sans» выше. */
  --oso-mono: ui-monospace, SFMono-Regular, Menlo, Consolas,
              "DejaVu Sans Mono", monospace;

  /* Полотно страницы. Веб — та же ночь, что под медвежонком; лист — бумага.
     Это ТОКЕН, а не два правила: body описан в blocks.css один раз. */
  --oso-canvas: var(--oso-bg);

  /* Кегль основного текста. На листе он в типографских пунктах (print.css) —
     15px на A4 дают рыхлую полосу. */
  --oso-size: 15px;

  /* Ширина полосы набора. На экране текст ограничен по читаемости, на листе
     ширину задают поля страницы — поэтому в print.css значение снимается. */
  --oso-measure: 68ch;

  /* Акценты по роли, а не по названию цвета. */
  --oso-accent: var(--oso-node);            /* номера шагов, активный пункт   */
  --oso-accent-soft: var(--oso-aurora-teal); /* надзаголовки, маркер раздела  */
  --oso-on-accent: var(--oso-night-0);      /* текст на акцентной заливке     */
  --oso-code-bg: rgba(7, 27, 58, .8);       /* подложка команды               */
  --oso-veil: rgba(7, 27, 58, .82);         /* стекло шапки и выпадающих слоёв */

  /* --- Словарь врезок по роли -----------------------------------------
     Роль названа СЛОВОМ в самой врезке (`.role`), цвет только подкрепляет:
     раздел про конфиденциальные данные обязан работать и в чёрно-белой
     распечатке. Имена ролей — часть источника оформления, разделы их не
     изобретают (docs/manual/README.md, гарантия #5 движка сборки). */
  --role-note: var(--oso-ice-deep);         /* замечание — нейтральное        */
  --role-trap: var(--oso-aurora-violet);    /* ловушка                        */
  --role-stop: var(--oso-alert);            /* предупреждение «так нельзя»    */
  --role-safe: var(--oso-aurora-teal);      /* безопасная альтернатива        */
  --tint-trap: rgba(165, 107, 255, .12);
  --tint-stop: rgba(255, 154, 143, .10);
  --tint-safe: rgba(56, 240, 192, .10);

  /* --- Разметка кадров ------------------------------------------------
     Два знака и ровно два смысла. Цвет взят с концов градиента сияния, но
     НЕ равен --oso-node: синим в продукте покрашены настоящие кнопки, и
     обводка того же цвета читалась бы как элемент интерфейса, а не как
     пометка поверх него. Форма дублирует цвет — кадр обязан читаться на
     чёрно-белой распечатке и у того, кто не различает цвета.

     Эти токены НЕ переопределяются на листе, и это главное в них: рамку на
     кадре нарисовал `scripts/qa/annotate.py` один раз, в один PNG, который
     уезжает и в веб, и в PDF. Легенда, перекрашенная «под бумагу», разошлась
     бы с тем, что нарисовано на кадре, — и не сходится это у читателя, а не у
     нас (`test_legend_matches_what_annotate_draws`). */
  --mark-click: var(--oso-aurora-violet);   /* круг, сплошная рамка   */
  --mark-check: var(--oso-aurora-teal);     /* квадрат, пунктир       */
  --mark-redact: var(--oso-ink);            /* ромб, сплошная заливка */
  --mark-ink: var(--oso-ink);               /* цифра на светлой метке */
  --mark-ink-inverse: var(--oso-fur);       /* цифра на тёмной метке  */

  /* --- Полярный ДЕНЬ: значения для листа ------------------------------
     Тёмный градиент напечатать нельзя — ни на бумаге, ни в PDF. Лист получает
     светлую производную ТОЙ ЖЕ палитры: чернила знака как текст, лёд как
     линейки, те же акценты, затемнённые до контраста на белом. По ролям эти
     значения раздаёт print.css — здесь только сами значения, потому что
     литералы цвета живут в одном файле. */
  --paper-bg: #ffffff;
  --paper-ink: #0d2136;
  --paper-ink-dim: #4a6076;
  --paper-surface: #ffffff;
  --paper-surface-strong: #eef4f9;
  --paper-border: #d3e2ee;
  --paper-border-strong: #b6cddd;
  --paper-link: #0f6fb5;
  --paper-node: #0f6fb5;
  --paper-teal: #0e7c6b;
  --paper-violet: #6c33cc;
  --paper-alert: #a3382c;
  --paper-rule: #7d99ad;
  --paper-code-bg: #f2f7fb;
  --paper-tint-trap: #f4eeff;
  --paper-tint-stop: #fdefec;
  --paper-tint-safe: #e9f7f3;
}

/* ═══ blocks.css ═══ */
/* Компоненты руководства — ОДНО описание на оба артефакта (issue #358).

   Этот файл читают и веб-страница (#204), и рендер PDF (#203): он входит в оба
   набора (`scripts/docs/manual_style.py`, LAYERS). Веб и печать расходятся
   ТОЛЬКО значениями токенов — здесь нет ни одного правила «а для печати
   иначе». Сторож — `test_web_and_pdf_render_the_same_set_of_blocks`: он
   сравнивает объявления каждого блока в двух наборах и валит сборку, если
   web.css или print.css завёл своё описание того же блока.

   Отсюда два ограничения на то, ЧЕМ здесь можно верстать:

   1. Никаких литералов цвета: цвет берётся ролью через var() (tokens.css).
   2. Ничего, чего не умеет WeasyPrint: раскладка блоков — потоковая,
      без grid и без flex. Сетка страницы, шапка и оглавление живут в web.css,
      потому что на листе их не существует вовсе. Раскладка на flex в общем
      файле означала бы PDF, в котором подпись под кадром съехала, — а увидели
      бы это уже в разосланном файле. */

/* ─── Полотно и текст ─────────────────────────────────────────────────── */

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: var(--oso-font);
  /* Кегль — токен: на экране он в пикселях, на листе в пунктах, но правило
     остаётся одно. */
  font-size: var(--oso-size);
  line-height: 1.65;
  color: var(--oso-text);
  background: var(--oso-canvas);
}

a { color: var(--oso-link); }

/* Полоса набора. На экране её задаёт читаемость, на листе — поля страницы;
   значение приходит токеном, поэтому правило одно. */
.doc > p,
.doc > ul,
.doc > ol,
.doc > h2,
.doc > h3,
.doc > h4,
.doc > .lead,
.box,
.steps { max-width: var(--oso-measure); }

h1 {
  font-size: 1.9rem;
  line-height: 1.2;
  margin: 0 0 .5rem;
}

/* ─── Колофон ─────────────────────────────────────────────────────────────
   Строка «что это, какая версия, когда собрано». В вебе она стоит под
   заголовком, на листе та же строка уезжает в колонтитул каждой страницы
   (`string-set` читает @page из print.css). Печатное свойство стоит здесь, а
   не в подложке печати, ровно поэтому: источник строки — сам колофон, и
   второго описания блока от этого не заводится, а веб `string-set`
   игнорирует. */

.colophon {
  margin: 0 0 2rem;
  font-size: .84em;
  color: var(--oso-text-dim);
  string-set: oso-colophon content(text);
}

/* Надзаголовок: «Раздел 03» и подобное — ориентир, а не заголовок. */
.eyebrow {
  margin: 0 0 .4rem;
  font-size: .74em;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--oso-accent-soft);
}

.lead {
  margin: 0 0 2rem;
  font-size: 1.08rem;
  color: var(--oso-text-dim);
}

h2 {
  font-size: 1.25rem;
  margin: 3rem 0 .2rem;
  padding-top: 1.6rem;
  border-top: 1px solid var(--oso-border);
}

h2 .no {
  display: block;
  margin-bottom: .4rem;
  font-family: var(--oso-mono);
  font-size: .62em;
  font-weight: 400;
  letter-spacing: .08em;
  color: var(--oso-text-dim);
}

h3 { font-size: 1.02rem; margin: 2rem 0 .1rem; }

/* Четвёртый уровень существует не для красоты: раздел бывает собран из
   НЕСКОЛЬКИХ сценариев (формат 2 индекса, #196) — тогда `#` это раздел,
   `##` подраздел, а шаг уходит на `###`. Без правила шаг такого раздела
   остался бы с браузерным умолчанием, то есть мельче обычного текста. */
h4 { font-size: .98rem; margin: 1.7rem 0 .1rem; color: var(--oso-text-dim); }

p { margin: .8rem 0; }
ul, ol { margin: .8rem 0; padding-left: 1.3em; }
li { margin: .3rem 0; }
li::marker { color: var(--role-note); }

/* ─── Пример команды ──────────────────────────────────────────────────────
   Блок словаря: «вот это наберите». Вид тот же, что у выданного ключа в
   кабинете (.secret), — команда и ключ читаются одинаково и там, и здесь. */

code {
  font-family: var(--oso-mono);
  font-size: .92em;
  background: var(--oso-code-bg);
  border: 1px solid var(--oso-border);
  border-radius: 5px;
  padding: .05em .3em;
}

pre {
  margin: 1.1rem 0;
  padding: 1rem;
  background: var(--oso-code-bg);
  color: var(--oso-accent-soft);
  border: 1px solid var(--oso-border);
  border-radius: var(--oso-radius-sm);
  font-family: var(--oso-mono);
  font-size: .88rem;
  line-height: 1.6;
  /* На экране команда прокручивается, на листе прокрутки нет — перенос по
     словам, иначе длинная команда уезжает за поле. */
  white-space: pre-wrap;
  word-wrap: break-word;
  break-inside: avoid;
}

pre code { background: none; border: 0; padding: 0; font-size: 1em; }

/* Комментарий внутри команды: его не набирают. */
pre .rem { color: var(--oso-text-dim); }

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

.scroll { overflow-x: auto; margin: 1.2rem 0; }

table {
  border-collapse: collapse;
  width: 100%;
  margin: 1.2rem 0;
  background: var(--oso-surface);
  break-inside: avoid;
}

th, td {
  text-align: left;
  vertical-align: top;
  padding: .55rem .65rem;
  border-bottom: 1px solid var(--oso-border);
}

th {
  font-size: .8em;
  text-transform: uppercase;
  color: var(--oso-text-dim);
}

td:first-child { font-weight: 600; }

/* ─── Врезки: роль названа словом, а не иконкой ───────────────────────────
   Четыре блока словаря, у каждого своё имя в источнике и свой вид в обоих
   артефактах:

     .box        — замечание («первым делом», «границы»);
     .box.trap   — ЛОВУШКА: так устроено, и это не сбой;
     .box.stop   — ПРЕДУПРЕЖДЕНИЕ: так делать нельзя;
     .box.safe   — БЕЗОПАСНАЯ АЛЬТЕРНАТИВА: а вот так можно.

   Пара stop/safe — половина раздела про конфиденциальные данные (#202): если
   «нельзя» и «можно» выглядят одинаково, раздел не работает. Различает их не
   только цвет: роль напечатана словом в `.role`, и в чёрно-белой распечатке
   читается она же. */

.box {
  margin: 1.4rem 0;
  padding: .85rem 1.1rem;
  background: var(--oso-surface);
  border: 1px solid var(--oso-border);
  border-left: 3px solid var(--role-note);
  border-radius: var(--oso-radius-sm);
  break-inside: avoid;
}

.box .role {
  display: block;
  margin-bottom: .25rem;
  font-size: .72em;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--oso-text-dim);
}

.box p { margin: 0; }
.box p + p { margin-top: .5rem; }

.box.trap { border-left-color: var(--role-trap); background: var(--tint-trap); }
.box.trap .role { color: var(--role-trap); }

.box.stop { border-left-color: var(--role-stop); background: var(--tint-stop); }
.box.stop .role { color: var(--role-stop); }

.box.safe { border-left-color: var(--role-safe); background: var(--tint-safe); }
.box.safe .role { color: var(--role-safe); }

/* Цитата из Markdown — то же замечание: у разделов нет способа задать класс,
   и `> …` обязан выглядеть блоком словаря, а не сиротой. */
blockquote {
  margin: 1.4rem 0;
  padding: .85rem 1.1rem;
  border: 1px solid var(--oso-border);
  border-left: 3px solid var(--role-note);
  border-radius: var(--oso-radius-sm);
  background: var(--oso-surface);
  color: var(--oso-text-dim);
}

/* ─── Кадры ───────────────────────────────────────────────────────────── */

figure { margin: 1.8rem 0; padding: 0; break-inside: avoid; }

.frame {
  background: var(--oso-surface);
  border: 1px solid var(--oso-border-strong);
  border-radius: var(--oso-radius);
  box-shadow: var(--oso-shadow);
  overflow: hidden;
}

/* Кадр стенда шире полосы набора: без max-width снимок 1280px уезжает за поля
   A4 — с этого и начиналась ловушка рендера PDF. */
img {
  max-width: 100%;
  height: auto;
  display: block;
  break-inside: avoid;
}

figcaption {
  margin-top: .7rem;
  font-size: .9em;
  color: var(--oso-text-dim);
}

.keys { margin: .45rem 0 0; padding: 0; list-style: none; }
.keys li, .keys span { display: block; margin: .25rem 0; line-height: 1.4; }

/* Знак разметки в легенде и в подписи: ТА ЖЕ форма и тот же цвет, что рисует
   scripts/qa/annotate.py на самом кадре. Форма несёт смысл наравне с цветом. */
.pin {
  display: inline-block;
  width: 1.25rem;
  height: 1.25rem;
  margin-right: .4rem;
  text-align: center;
  line-height: 1.25rem;
  font-style: normal;
  font-size: .74rem;
  font-weight: 700;
}

.pin.click {
  background: var(--mark-click);
  color: var(--mark-ink);
  border-radius: 50%;
}

.pin.check {
  background: var(--mark-check);
  color: var(--mark-ink);
  border-radius: 4px;
}

.pin.redact {
  background: var(--mark-redact);
  color: var(--mark-ink-inverse);
  border-radius: 2px;
  transform: rotate(45deg);
}

/* Откуда кадр: имя сценария и шага. */
.src { font-family: var(--oso-mono); font-size: .78em; color: var(--oso-text-dim); }

/* ─── Шаги ────────────────────────────────────────────────────────────────
   Нумерация — счётчиком, раскладка — потоком с абсолютным номерком: то же
   самое на flex/grid WeasyPrint разложил бы иначе, чем браузер. */

.steps {
  list-style: none;
  margin: 1.2rem 0;
  padding: 0;
  counter-reset: st;
}

.steps > li {
  position: relative;
  margin: 0;
  padding: 0 0 1.1rem 2.5rem;
  break-inside: avoid;
}

.steps > li::before {
  counter-increment: st;
  content: counter(st);
  position: absolute;
  left: 0;
  top: .1rem;
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 50%;
  text-align: center;
  line-height: 1.6rem;
  background: var(--oso-accent);
  color: var(--oso-on-accent);
  font-size: .82rem;
  font-weight: 700;
}

.steps > li > :first-child { margin-top: 0; }
.steps > li > :last-child { margin-bottom: 0; }

/* ═══ web.css ═══ */
/* Веб-вид руководства: тёмный, как кабинет (issue #358, потребитель — #204).

   Здесь ТОЛЬКО то, чего на листе не существует: обвязка страницы (шапка, поиск,
   оглавление, сетка, подвал) и декоративный слой сияния. Ни одного блока
   документа: заголовки, врезки, кадры, команды и таблицы описаны один раз в
   blocks.css и оттуда попадают в оба артефакта. Правило сторожит
   `test_web_and_pdf_render_the_same_set_of_blocks` — правило `.box.stop`,
   заведённое здесь, валит сборку.

   Цвет — только ролью через var(). Внешних шрифтов, CDN и @import нет:
   страница обязана открываться в закрытом периметре
   (`test_manual_page_loads_without_the_network`). */

/* Тема одна на светлую и тёмную схему ОС, намеренно: знак нарисован «белым на
   холодном» и на светлой подложке теряет края (docs/brand/README.md). Ровно
   так же поступает кабинет. */
html {
  color-scheme: dark;
  scroll-behavior: smooth;
  scroll-padding-top: 5.5rem;
}

/* Полоса сияния — декоративный слой поверх полотна, а не фон блока. */
body::before {
  content: "";
  position: fixed;
  inset: 0 0 auto 0;
  height: 70vh;
  background: var(--oso-bg-aurora);
  pointer-events: none;
  z-index: 0;
}

header, .shell, footer { position: relative; z-index: 1; }

:focus-visible { outline: none; box-shadow: var(--oso-focus); border-radius: 6px; }

/* Подпись, которую читает только экранный диктор: у поля поиска в шапке нет
   видимой подписи (её роль играет placeholder), а без <label> поле остаётся
   безымянным. Веб-only: на листе ни поля, ни диктора. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ─── Шапка: та же конструкция, что в кабинете ─────────────────────────── */

header {
  position: sticky;
  top: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  padding: .85rem 1.5rem;
  border-bottom: 1px solid var(--oso-border);
  background: var(--oso-veil);
  backdrop-filter: blur(12px) saturate(140%);
}

header .brand {
  display: inline-flex;
  align-items: center;
  gap: .6rem;
  flex: none;
  color: var(--oso-text);
  font-weight: 600;
  text-decoration: none;
}

header .brand svg { width: 30px; height: 30px; border-radius: 7px; display: block; }
header nav { display: flex; gap: 1rem; flex: none; }
header nav a { color: var(--oso-link); text-decoration: none; font-size: .92em; }
header nav a[aria-current] { color: var(--oso-text); font-weight: 600; }

/* ─── Поиск по заранее собранному индексу (#204) ───────────────────────── */

.find { position: relative; flex: 1 1 18rem; max-width: 30rem; margin-left: auto; }

.find input {
  width: 100%;
  min-width: 0;
  padding: .45rem 2.4rem .45rem 2.1rem;
  border: 1px solid var(--oso-border-strong);
  border-radius: var(--oso-radius-sm);
  background: var(--oso-veil);
  color: var(--oso-text);
  font: inherit;
  /* 16px — не про кегль, а про телефон: поле ввода мельче 16px заставляет iOS
     Safari зумить страницу при фокусе, и читатель остаётся на увеличенной
     странице, которую дальше двигает вбок. Кегль набора (--oso-size) здесь не
     годится: поиск — единственная причина, по которой веб-версия существует
     (#204), и на телефоне он не должен ломать раскладку. Правило веб-only:
     на листе поля поиска не существует. */
  font-size: max(16px, 1rem);
}

.find input::placeholder { color: var(--oso-text-dim); }
.find input:focus-visible { outline: none; box-shadow: var(--oso-focus); }

.find .glass {
  position: absolute;
  left: .7rem;
  top: .62rem;
  width: 15px;
  height: 15px;
  color: var(--oso-text-dim);
  pointer-events: none;
}

.find .key {
  position: absolute;
  right: .55rem;
  top: .5rem;
  font-family: var(--oso-mono);
  font-size: .72em;
  color: var(--oso-text-dim);
  border: 1px solid var(--oso-border);
  border-radius: 5px;
  padding: 0 .35rem;
  pointer-events: none;
}

.hits {
  position: absolute;
  top: 2.7rem;
  left: 0;
  right: 0;
  display: none;
  padding: .35rem;
  max-height: 60vh;
  overflow-y: auto;
  background: var(--oso-veil);
  border: 1px solid var(--oso-border-strong);
  border-radius: var(--oso-radius);
  box-shadow: var(--oso-shadow);
}

.hits.open { display: block; }

.hits button {
  display: block;
  width: 100%;
  margin: 0;
  padding: .5rem .65rem;
  text-align: left;
  cursor: pointer;
  background: none;
  border: 0;
  border-radius: var(--oso-radius-sm);
  color: var(--oso-text);
  font: inherit;
}

.hits button:hover, .hits button:focus-visible {
  background: var(--oso-surface-strong);
  outline: none;
}

.hits .h-sec {
  display: block;
  font-size: .72em;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--oso-accent-soft);
  font-weight: 600;
}

.hits .h-tit { display: block; font-weight: 600; margin-top: .1rem; }

.hits .h-snip {
  display: block;
  overflow: hidden;
  margin-top: .15rem;
  color: var(--oso-text-dim);
  font-size: .88em;
  line-height: 1.45;
}

.hits mark { background: var(--oso-surface-strong); color: inherit; border-radius: 3px; }
.hits .none { padding: .8rem .65rem; color: var(--oso-text-dim); font-size: .9em; }

/* ─── Каркас страницы ──────────────────────────────────────────────────── */

.shell {
  display: grid;
  grid-template-columns: 15rem minmax(0, 1fr);
  gap: 2.5rem;
  align-items: start;
  max-width: 1180px;
  margin: 2.5rem auto;
  padding: 0 1.5rem;
}

.doc { min-width: 0; }

.toc { position: sticky; top: 5.5rem; font-size: .94em; }

.toc .cap {
  margin: 1.4rem 0 .5rem;
  font-size: .72em;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--oso-text-dim);
  font-weight: 600;
}

.toc .cap:first-child { margin-top: 0; }

.toc a {
  display: flex;
  gap: .6rem;
  align-items: baseline;
  padding: .3rem .6rem;
  margin-left: -.6rem;
  border-radius: var(--oso-radius-sm);
  color: var(--oso-text-dim);
  text-decoration: none;
  line-height: 1.4;
}

.toc a:hover { background: var(--oso-surface); color: var(--oso-text); }

.toc a.here {
  background: var(--oso-surface-strong);
  color: var(--oso-text);
  font-weight: 600;
  box-shadow: inset 2px 0 0 var(--oso-accent-soft);
}

.toc a .no {
  flex: none;
  font-family: var(--oso-mono);
  font-size: .8em;
  color: var(--oso-text-dim);
}

.toc a.here .no { color: var(--oso-accent-soft); }

/* Подраздел в оглавлении: раздел из нескольких сценариев («Наши агенты» —
   это два разных агента) без своей строки выглядит одним. Веб-only:
   на листе оглавление печатает рендер PDF, и сетки страницы там нет. */
.toc a.sub { padding-left: 1.9rem; font-size: .95em; }

/* ─── Подвал: из какого коммита собрано ────────────────────────────────── */

footer {
  max-width: 1180px;
  margin: 2.5rem auto 3rem;
  padding: 1.2rem 1.5rem 0;
  border-top: 1px solid var(--oso-border);
  color: var(--oso-text-dim);
  font-size: .85em;
  display: flex;
  flex-wrap: wrap;
  gap: .3rem 1.6rem;
}

footer .m { font-family: var(--oso-mono); }

/* ─── Узкие экраны: половина прочтений будет с телефона (DoD #204) ─────── */

@media (max-width: 62rem) {
  .shell { grid-template-columns: minmax(0, 1fr); gap: 1.6rem; }
  .toc {
    position: static;
    order: 2;
    border-top: 1px solid var(--oso-border);
    padding-top: 1.2rem;
  }
}

@media (max-width: 40rem) {
  header { gap: .6rem; padding: .7rem 1rem; }
  header nav { display: none; }
  .find { flex-basis: 100%; margin-left: 0; max-width: none; }
  .find .key { display: none; }
  .shell { margin: 1.5rem auto; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}
