/* Акцент КАК ТЕКСТ — отдельно от акцента как заливки.

   В светлой теме канон не переопределяет акцент вовсе: он остаётся мятным
   (#61ffbb), задуманным для тёмного фона. Как заливка кнопки это верно —
   мятная кнопка с тёмной подписью читается. Как ЦВЕТ ТЕКСТА на белом это
   контраст 1,27 при норме 4,5: ссылки продукта в светлой теме нечитаемы.
   Измерено на стенде, не на глаз.

   Пока в каноне нет светлого акцента, текст берёт фирменный цвет — он
   канонный, не выдуманный, и даёт на белом 6,34. Своего значения здесь не
   заводится: выбрать светлый акцент — решение оформителя, и вопрос вынесен
   владельцу. */
:root { --accent-text: var(--color-accent); }
.light { --accent-text: var(--color-brand); }

/* Вид продукта. Значения берутся из порождённых токенов, а не заводятся
   здесь: своё значение рядом с каноном — это вторая копия канона, которая
   разойдётся с ним молча.

   Имена берутся из порождённых токенов (`src/shared/tokens/tokens.css`).
   Здесь жила вторая, более ранняя система имён — `--surface-0/1`,
   `--text-primary`, `--divider`, `--space-*`, `--radius-s/m`,
   `--font-size-*`. В токенах клиента таких имён нет вовсе, поэтому ни одно
   из этих правил не подставляло значение: фон страницы брался у браузера,
   поля оставались без поверхности, и целая вёрстка выглядела черновой.
   Имена сведены к каноническим. */
body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  margin: 0;
  padding: 16px;
}

.ui-button {
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  padding: 8px 14px;
  font: inherit;
  cursor: pointer;
}

/* Фокус виден на каждом интерактивном элементе: это требование, а не вкус.
   Правило написано на ВИДЫ элементов, а не на три класса. Раньше здесь стояли
   `.ui-button`, `.ui-field__input` и `.ui-list__pick`, и комментарий обещал
   больше, чем делал код: всему остальному — кнопкам без класса во внутреннем
   контуре, ссылкам, спискам выбора — кольцо рисовал браузер. Оно видно, но
   оно чужое: у каждого браузера свой цвет и своя толщина, и за контраст на
   тёмном фоне не отвечает никто.
   Проверено НАЖАТИЕМ клавиши, а не чтением стилей: программный фокус кольца
   не даёт вовсе, и по нему легко объявить дырой то, чего нет. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible,
[tabindex]:not([tabindex="-1"]):focus-visible {
  outline: 2px solid var(--color-mint);
  outline-offset: 2px;
}

.ui-state { border: 1px solid var(--hairline); border-radius: var(--radius-lg); padding: 12px; }
.ui-state__title { color: var(--text); margin: 0 0 4px; }
.ui-state__hint { color: var(--text-muted); margin: 0 0 8px; }

/* Скрытое — скрыто, чем бы оно ни было.
   
   `[hidden]` браузер выполняет через `display: none`, и любой СВОЙ `display`
   его перебивает. Так и вышло: поле кода второго фактора должно появляться
   только по отказу службы, а стояло на входе всегда — `.ui-field` объявлен
   сеткой, и она сильнее.
   
   Правило общее и стоит первым: те же грабли уже были у окна и у пометки
   «в разработке», и оба раза их чинили поштучно. */
[hidden] { display: none !important; }

.ui-field { display: grid; gap: 4px; margin-bottom: 12px; }
.ui-field__label { color: var(--text-muted); }
.ui-field__input {
  background: var(--field);
  color: var(--text);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  padding: 8px;
  font: inherit;
}
.ui-field__input::placeholder { color: var(--text-subtle); }
.ui-field__hint { color: var(--text-muted); margin: 0; }

.ui-list__rows { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }

.ui-notice { border-left: 3px solid var(--hairline); padding: 8px 12px; }
.ui-notice--success { border-left-color: var(--color-mint); }
.ui-notice--failure { border-left-color: var(--danger); }

.ui-modal {
  position: fixed;
  inset: 0;
  background: var(--overlay);
  display: grid;
  place-items: center;
}
.ui-modal[hidden] { display: none; }
.ui-modal__box {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  padding: 16px;
  display: grid;
  gap: 8px;
}

/* Контур доступа. Цвета только токенами: hex в разметке переживает смену темы
   и остаётся чужим пятном. */
/* Внутренние экраны продукта — поддержка, согласия, свои данные, жалобы,
   второй фактор, удаление учётной записи, ограничение. Своих кадров у них в
   макете «Этапа 1» нет.
   
   Этот класс носят девять экранов сразу, поэтому вид задаётся здесь один раз,
   а не переписыванием девяти файлов: колонка содержимого 756 по центру,
   карточка с отступом 24 и скруглением карточки — те же числа, что у
   измеренных экранов. По макету не сверено. */
.ui-screen {
  box-sizing: border-box;
  display: grid;
  gap: 12px;
  width: 756px;
  max-width: 100%;
  margin: 24px auto;
  padding: 24px;
  color: var(--text);
  background: var(--surface);
  border-radius: var(--radius-card);
}

.ui-screen__title {
  margin: 0 0 4px;
  font: var(--text-title-fw, 650) var(--text-title) / var(--text-title-lh) var(--font-sans);
  letter-spacing: var(--text-title-ls);
  color: var(--text);
}

/* Второй заголовок внутри экрана — это раздел, а не заголовок страницы. */
.ui-screen h2.ui-screen__title {
  margin: 12px 0 0;
  font: var(--text-heading-fw, 600) var(--text-heading) / var(--text-heading-lh)
        var(--font-sans);
}

/* Списки внутренних экранов опознаются ПРИЗНАКОМ, а не классом. Правила
   висели на `.ui-list`, которого внутренние экраны не носят: они написаны
   голыми тегами, и список помечен только `data-items`. Из-за этого очередь
   разбора выводилась браузерным списком — точки-маркеры и отступ 40. Признак
   и есть то, чем список опознаётся; классы там появиться не должны. */
.ui-screen [data-list] { display: grid; gap: 8px; }
/* Зазор нулевой намеренно: строки такого списка разделены волоском, а не
   пустотой, и зазор разорвал бы разделители на куски. */
.ui-screen [data-items] {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0;
}

.ui-screen [data-items] li {
  padding: 12px 0;
  border-bottom: 1px solid var(--hairline);
  font: var(--text-label) / var(--text-label-lh) var(--font-sans);
}

.ui-screen [data-items] li:last-child { border-bottom: 0; }

/* Строка списка внутреннего экрана. Её части — номер, предмет, код причины —
   стояли встык: «Жалоба №1042Записьspam_or_ads». Промежуток задаётся
   раскладкой, а не пробелами в разметке: пробел в разметке это текст, а
   текстам место в словаре. */
/* Отклик под записью. Класс стоял в разметке, а правил у него не было ни
   одного: кнопка и счётчик слипались в «Отклик0». Счётчик подписан не словом,
   а местом — он стоит вплотную к своей кнопке и относится к ней. */
.ui-reactions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 4px 0 8px;
}

.ui-reactions [data-count-view] {
  color: var(--text-subtle);
  font: var(--text-label) / var(--text-label-lh) var(--font-sans);
}

.ui-reactions[data-mine="true"] [data-count-view] { color: var(--accent-text); }

/* Кнопки в ряд. Стояли ВСТЫК на четырёх экранах: «Взять в разборРешить»,
   «Взять в работуОтветить заявителю», «ВыключитьВыпустить новые». Обёртки у
   них нет, поэтому зазор задаётся соседством — правило работает для любого
   числа кнопок подряд.

   Найдено восьмым правилом обхода; глазами я один из этих случаев уже видел и
   принял за искажение при извлечении текста. */
.ui-screen button + button { margin-left: 8px; }

/* Предмет жалобы: чей он и где лежит. Автор и место стояли встык —
   «@miraКухня по выходным», — потому что у карточки не было ни раскладки, ни
   зазора. Четвёртый такой случай за ночь, и по нему обход доучен отдельным
   правилом: слипшийся текст не ловился ничем. */
[data-target-kind] {
  display: grid;
  gap: 4px;
  padding: 12px;
  background: var(--surface-2);
  border-radius: var(--radius-card);
}

[data-target-author] { color: var(--text-subtle); }

[data-target-place] {
  color: var(--text-subtle);
  font: var(--text-caption) / var(--text-caption-lh) var(--font-sans);
}

[data-target-text] { margin: 0; }

/* Шапка обращения: когда пришло, от кого, закрыто ли. Куски стояли встык —
   «3 сентября 2026 г.Автор: гость», — потому что у контейнера не было ни
   раскладки, ни зазора. Третий такой случай за вечер: разметка размечена, вида
   нет. */
/* Шапка разбора и шапка обращения — одна и та же работа в двух местах
   контура, и одеты они одинаково. У разбора шапки не было вовсе; когда она
   появилась, куски пошли встык — «Запись» и «Опознаватель предмета» слились
   в одно слово. Промежуток здесь несущий, а не оформление. */
[data-review-head],
[data-ticket-head] {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  margin: 0 0 8px;
  color: var(--text-subtle);
  font: var(--text-caption) / var(--text-caption-lh) var(--font-sans);
}

/* Стороны переписки поддержки. Различие было записано признаком `data-side` и
   не имело вида: реплика заявителя и ответ поддержки выглядели одинаково, а
   различать их — и есть работа на этом экране.

   Внутренняя заметка отделена сильнее прочего: сотрудник, перепутавший её с
   ответом, отправит заявителю то, что писал коллеге. */
.ui-screen [data-side] { display: block; }

.ui-screen [data-message-side] {
  display: block;
  margin-bottom: 2px;
  color: var(--text-subtle);
  font: var(--text-caption) / var(--text-caption-lh) var(--font-sans);
}

.ui-screen [data-side="support"] [data-message-side] { color: var(--accent-text); }

.ui-screen [data-side="internal"] {
  padding-left: 10px;
  border-left: 2px dashed var(--hairline-strong, var(--hairline));
}

.ui-screen [data-side="internal"] [data-message-body] { color: var(--text-subtle); }

.ui-screen .row {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 8px;
  align-items: baseline;
}

/* Ссылки без класса. Третий случай той же болезни за ночь после полей и
   кнопок: голая ссылка берёт вид у браузера — синяя `rgb(0, 0, 238)` с
   подчёркиванием, на тёмной карточке почти нечитаемая. Вид тот же, что у
   ссылок-имён в списках клиента. */
.ui-screen a:not([class]) {
  color: var(--text);
  text-decoration: none;
}

.ui-screen a:not([class]):hover { color: var(--accent-text); }

/* Списки без класса. Кроме помеченных `data-items` во внутренних экранах есть
   ещё четыре: последствия удаления, запасные коды, чёрный список, правила
   дома. Первые три — перечисления, и точка-маркер там от браузера.
   ПРАВИЛА ДОМА — исключение: номер срезается из текста и рисуется списком,
   то есть номер здесь содержание, а не украшение, и человек ссылается на
   «правило 3». Поэтому `ul` маркеры теряет, а `ol` их сохраняет и получает
   только отступ и промежуток. */
/* Списки с признаком `data-items` исключены: у них свои разделители. */
.ui-screen ul:not([class]):not([data-items]) {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 4px;
}

.ui-screen ol:not([class]) {
  margin: 0;
  padding-inline-start: 20px;
  display: grid;
  gap: 4px;
}

.ui-screen ol:not([class]) li { padding-inline-start: 2px; }

/* Видимая подпись поля. Во внутреннем контуре подписи были заведены скрытыми:
   правило «поле без имени» они закрывали, а человек видел «72» и не знал, что
   это часы. На экране, где решается ограничение чужой учётной записи, цена
   догадки слишком велика. */
.ui-screen label:not(.ui-hidden) {
  display: block;
  margin-top: 4px;
  color: var(--text-subtle);
  font: var(--text-caption) / var(--text-caption-lh) var(--font-sans);
}

/* Поля без класса. На части внутренних экранов поля ввода написаны голыми
   тегами, без `ui-field__input`, — и получали белый фон от браузера: белая
   полоса посреди тёмной карточки. Правило ловит их по тегу, а не по классу,
   потому что классов там нет. Флажки и выбор файла исключены: у них своя
   отрисовка, и общий фон её ломает. */
.ui-screen input:not([type="checkbox"]):not([type="radio"]):not([type="file"]),
.ui-screen textarea,
.ui-screen select {
  box-sizing: border-box;
  width: 100%;
  padding: 10px 14px;
  color: var(--text);
  background: var(--field);
  border: 0;
  border-radius: var(--radius-xl);
  font: var(--text-body) / var(--text-body-lh) var(--font-sans);
}

.ui-screen textarea { min-height: 110px; resize: vertical; }

/* Главное действие экрана — акцентом.
   
   Отличить его правилом «кнопка отправляет форму» нельзя: формы здесь
   отправляются обработчиком, и у кнопок стоит явный `type="button"`.
   Поэтому действия перечислены списком — он конечен и снят с самих экранов.
   Отменяющие и снимающие действия (отказ, отзыв, выход отовсюду) в список
   намеренно не входят: акцент зовёт нажать, а звать нажать на снятие доступа
   не нужно. */
.ui-screen .ui-button[data-action="submit"],
.ui-screen .ui-button[data-action="send"],
.ui-screen .ui-button[data-action="publish"],
.ui-screen .ui-button[data-action="request"],
.ui-screen .ui-button[data-action="confirm"],
.ui-screen .ui-button[data-action="enrol"],
.ui-screen .ui-button[data-action="appeal"],
.ui-screen .ui-button[data-action="reply"],
.ui-screen .ui-button[data-action="accept"],
.ui-screen .ui-button[data-action="approve"] {
  justify-self: start;
  color: var(--on-mint);
  background: var(--color-accent);
  border-color: transparent;
  border-radius: var(--radius-pill);
  padding: 10px 20px;
  font-weight: 600;
}

.ui-screen .ui-button[data-action="submit"]:hover,
.ui-screen .ui-button[data-action="send"]:hover,
.ui-screen .ui-button[data-action="publish"]:hover,
.ui-screen .ui-button[data-action="request"]:hover,
.ui-screen .ui-button[data-action="confirm"]:hover,
.ui-screen .ui-button[data-action="enrol"]:hover,
.ui-screen .ui-button[data-action="appeal"]:hover,
.ui-screen .ui-button[data-action="reply"]:hover,
.ui-screen .ui-button[data-action="accept"]:hover,
.ui-screen .ui-button[data-action="approve"]:hover {
  background: var(--color-accent-hover);
}

.ui-form {
  display: grid;
  gap: 12px;
}

.ui-field__error {
  margin: 0;
  color: var(--danger);
  font: var(--text-caption) / var(--text-caption-lh) var(--font-sans);
}

.ui-field__error:empty { display: none; }

.ui-form__error {
  margin: 0;
  color: var(--danger);
  font: var(--text-label) / var(--text-label-lh) var(--font-sans);
}

.ui-form__state {
  margin: 0;
  color: var(--text-muted);
  font: var(--text-caption) / var(--text-caption-lh) var(--font-sans);
}

.ui-notice {
  margin: 0;
  padding: 8px;
  color: var(--text);
  background: var(--surface-2);
}

.ui-screen__links {
  display: flex;
  gap: 12px;
}

.ui-link {
  color: var(--accent-text);
}

/* ------------------------------------------------------------------
   Оболочка приложения. Размеры сняты с выгрузки макета 1:1 (экран
   «личный чат», 1440×1098): зазор фона 5, лента значков 73, колонка
   раздела 295, верхняя строка 64, шапка раздела 73.

   Цвета — только именами токенов: hex здесь означает копию канона,
   которая разойдётся с ним молча.
   ------------------------------------------------------------------ */
.shell {
  --shell-gap: 5px;
  --shell-rail: 73px;
  --shell-side: 295px;
  --shell-top: 64px;

  display: grid;
  grid-template-columns: var(--shell-rail) 1fr;
  /* Зазора между лентой значков и колонкой раздела в макете НЕТ: они одна
     панель. Пять точек — это отступ страницы и зазор до основной области, а
     не расстояние между всеми колонками. Проверено по кадру «личный чат»:
     панель занимает 5..372 (73 + 295 подряд), затем 373..377 фон, затем
     основная область. */
  gap: 0;
  /* Высота, а не минимальная высота. Лента значков, колонка раздела и
     основная область объявлены прокручиваемыми внутри себя, а это работает
     только при ограниченной высоте рамки. С `min-height` рамка растёт вместе
     с содержимым, внутренняя прокрутка не включается — и на длинном экране
     (профиль — первый такой) уезжает вся страница вместе с лентой значков и
     строкой поиска. */
  height: 100vh;
  padding: var(--shell-gap);
  background: var(--bg);
  box-sizing: border-box;
  overflow: hidden;
}

.shell--with-side {
  grid-template-columns: var(--shell-rail) var(--shell-side) 1fr;
}

/* Лента значков. Своя поверхность, а не прозрачная полоса: в макете она
   отделена от фона и держит скругление вместе с колонкой раздела. */
.shell__rail {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 10px 0;
  background: var(--rail, var(--surface));
  border-radius: var(--radius-card);
  overflow-y: auto;
  scrollbar-width: thin;
}

/* С колонкой раздела лента образует с ней одну панель: смежные углы прямые,
   иначе на стыке видны две выемки, которых в макете нет. */
.shell--with-side .shell__rail {
  border-radius: var(--radius-card) 0 0 var(--radius-card);
}

.shell--with-side .shell__side {
  border-radius: 0 var(--radius-card) var(--radius-card) 0;
}

.shell__rail-item {
  position: relative;
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  color: var(--text-muted);
  background: transparent;
  border: 0;
  border-radius: var(--radius-nav, 10px);
  cursor: pointer;
  text-decoration: none;
}

.shell__rail-item:hover { background: var(--nav-hover-bg, var(--surface-2)); }

.shell__rail-item.is-active {
  color: var(--text);
  background: var(--nav-icon-active-bg, var(--nav-active-bg, var(--surface-2)));
}

.shell__rail-line {
  width: 32px;
  height: 1px;
  margin: 4px 0;
  background: var(--hairline, var(--divider));
}

.shell__icon { width: 22px; height: 22px; }

/* Счётчик. Не сдвигает значок и не растягивает ленту: он поверх. */
.shell__badge {
  position: absolute;
  top: 2px;
  right: 2px;
  min-width: 18px;
  padding: 0 5px;
  font: 600 11px/18px var(--font-sans);
  color: var(--on-mint);
  text-align: center;
  background: var(--color-notify, var(--color-mint));
  border-radius: var(--radius-pill, 900px);
}

/* Колонка раздела: список переписок, список друзей. */
.shell__side {
  display: flex;
  flex-direction: column;
  min-width: 0;
  background: var(--surface);
  border-radius: var(--radius-card, 14px);
  overflow: hidden;
}

.shell__side-top {
  display: flex;
  align-items: center;
  height: var(--shell-top);
  padding: 0 16px;
  border-bottom: 1px solid var(--hairline, var(--divider));
}

.shell__side-title {
  margin: 0;
  font: var(--text-heading-fw, 600) var(--text-heading, 1.125rem)
        / var(--text-heading-lh, 1.5rem) var(--font-sans);
  color: var(--text);
}

.shell__side-body { flex: 1; min-height: 0; overflow-y: auto; }

/* Основная область — ФОН страницы, а не поверхность. Проверено на двух
   независимых кадрах: и в «личном чате», и в «профиле» сразу за колонкой идёт
   #070C16, а поверхностями лежат только верхняя строка, шапка переписки и
   строка ввода. Пока здесь стояла поверхность, карточки экрана исчезали:
   поверхность на поверхности того же цвета. */
.shell__main {
  display: flex;
  flex-direction: column;
  min-width: 0;
  margin-left: var(--shell-gap);
  background: transparent;
  overflow: hidden;
}

.shell__top {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: none;
  height: var(--shell-top);
  padding: 0 16px;
  background: var(--surface);
  border-radius: var(--radius-card);
}

.shell__flat {
  display: grid;
  place-items: center;
  position: relative;
  width: 36px;
  height: 36px;
  color: var(--text-muted);
  background: transparent;
  border: 0;
  border-radius: var(--radius-nav, 10px);
  cursor: pointer;
  text-decoration: none;
  flex: none;
}

.shell__flat:hover { background: var(--nav-hover-bg, var(--surface-2)); }

/* Поиск занимает всё свободное место и стоит по центру строки — как в
   макете, где он шире всех остальных частей шапки вместе. */
.shell__search {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
  min-width: 0;
  max-width: 640px;
  margin: 0 auto;
  padding: 0 14px;
  height: 40px;
  color: var(--text-subtle);
  background: var(--field, var(--surface-2));
  border-radius: var(--radius-pill, 900px);
}

/* Подпись поля нужна чтению вслух, а на экране её в макете нет. */
.shell__search-label {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.shell__search-input {
  flex: 1;
  min-width: 0;
  color: var(--text);
  background: transparent;
  border: 0;
  font: var(--text-body, .9375rem) / var(--text-body-lh, 1.45rem) var(--font-sans);
}

.shell__search-input::placeholder { color: var(--text-subtle); }
.shell__search-input:focus { outline: none; }
.shell__search:focus-within { outline: 2px solid var(--color-mint); outline-offset: 2px; }

.shell__create {
  flex: none;
  padding: 9px 16px;
  color: var(--on-mint);
  background: var(--color-accent, var(--color-mint));
  border-radius: var(--radius-pill, 900px);
  font: 600 var(--text-label, .8125rem) / 1 var(--font-sans);
  text-decoration: none;
}

.shell__create:hover { background: var(--color-accent-hover, var(--color-mint)); }

/* Цвет задан намеренно: внутри ссылки сейчас только значок, и браузерная
   синева не видна — но она унаследуется любым текстом, который здесь
   появится. Обход находит такую ссылку по цвету умолчания. */
.shell__me { flex: none; text-decoration: none; color: var(--text); }

.shell__me-face {
  display: block;
  width: 36px;
  height: 36px;
  border-radius: var(--radius-pill, 900px);
  background: var(--grad-avatar, var(--surface-2));
}

/* Полоса ограничения. Предупреждающая, а не тревожная: ограничение — это
   положение дел, о котором надо знать, а не поломка. Красный цвет здесь
   читался бы как сбой сервиса и отправлял бы человека жаловаться не туда. */
/* Выбор видимости записи. Рамки у поля нет: набор из двух отметок — не
   карточка внутри карточки, и вторая рамка только дробит форму. */
/* Пометка скрытой записи. Приглушённая рамка, а не тревожный цвет: закрытая
   запись — это выбор человека, а не беда. */
.post__closed {
  margin: 0 0 8px;
  padding: 6px 10px;
  border: 1px solid var(--border-card);
  border-radius: var(--radius-lg);
  color: var(--text-muted);
  font-size: 13px;
}

.page__choice {
  display: flex;
  gap: 16px;
  align-items: center;
  margin: 0;
  padding: 0;
  border: 0;
}

.page__choice-one {
  display: inline-flex;
  gap: 6px;
  align-items: center;
  color: var(--text);
  font-size: 13px;
}

.shell__restriction {
  margin-top: var(--shell-gap);
  padding: 10px 14px;
  border: 1px solid var(--warning);
  border-radius: var(--radius-lg);
  background: var(--warning-soft);
  color: var(--text);
  font-size: 13px;
  line-height: 1.45;
}

.shell__content { flex: 1; min-height: 0; margin-top: var(--shell-gap); overflow-y: auto; }

/* Узкие окна: колонка раздела уходит, лента значков остаётся. Полноценная
   мобильная раскладка макетом не покрыта — здесь только защита от
   горизонтальной прокрутки страницы. */
@media (max-width: 900px) {
  .shell, .shell--with-side { grid-template-columns: var(--shell-rail) 1fr; }
  .shell__side { display: none; }
  /* Кнопка сворачивания колонки раздела здесь не нужна: самой колонки нет. */
  .shell__top [data-shell-collapse] { display: none; }
}

/* Узкое окно: верхняя строка перестаёт помещаться в один ряд, и поиск
   сжимается до неиспользуемой полоски — на 400 точках от него оставалось
   меньше сорока. Поиск уходит на свою строку целиком.
   
   «Создать» при этом не прячется: плюс в ленте значков ведёт на создание
   дома, а не записи, и спрятав кнопку, мы отняли бы единственный вход в
   создание записи.
   
   Мобильной раскладки в макете «Этапа 1» нет вовсе, поэтому здесь решается
   одно: чтобы не ломалось. Появится кадр — сверить. */
@media (max-width: 620px) {
  .shell__top {
    height: auto;
    min-height: var(--shell-top);
    flex-wrap: wrap;
    padding: 8px 12px;
  }

  .shell__search {
    order: 3;
    flex-basis: 100%;
    max-width: none;
    margin: 0;
  }
}

/* Узкое окно и переписки: колонка раздела здесь спрятана правилом выше, а она
   и есть список диалогов — выбрать переписку становится нечем.
   
   Поэтому на узком окне показывается ЛИБО список, ЛИБО открытая переписка:
   пустая область переписки (`.talks__empty` — «выберите переписку слева»)
   означает, что выбирать ещё не из чего, и тогда нужен список. Обратный путь
   есть: пункт «Переписки» в ленте значков ведёт на адрес без переписки, то
   есть снова к списку. */
@media (max-width: 900px) {
  .shell--with-side:has(.talks__empty) .shell__side { display: flex; }
  .shell--with-side:has(.talks__empty) .shell__main { display: none; }
}

/* ------------------------------------------------------------------
   «В разработке»: окно и блок раздела.
   ------------------------------------------------------------------ */
/* Стендовая строка: код подтверждения на экране, пока нет почтового канала.
   Отличается от обычного уведомления рамкой предупреждения — человек должен
   видеть, что так быть не должно, иначе покажется, что продукт печатает коды
   на экране по замыслу. */
.ui-notice--stand {
  border: 1px dashed var(--warning);
  border-radius: var(--radius-md);
  padding: 8px 12px;
  color: var(--text);
}

.ui-notice--stand b {
  font-family: var(--font-mono);
  letter-spacing: .08em;
}

.ui-pending {
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 16px;
  background: var(--overlay, rgba(0, 0, 0, .6));
  z-index: 50;
}

.ui-pending[hidden] { display: none; }

.ui-pending__box {
  max-width: 420px;
  padding: 20px;
  background: var(--surface-2);
  border: 1px solid var(--border-card, var(--divider));
  border-radius: var(--radius-card, 14px);
  box-shadow: var(--elev-pop, none);
}

.ui-pending__title {
  margin: 0 0 8px;
  color: var(--text);
  font: var(--text-heading-fw, 600) var(--text-heading, 1.125rem)
        / var(--text-heading-lh, 1.5rem) var(--font-sans);
}

.ui-pending__why {
  margin: 0 0 16px;
  color: var(--text-muted);
  font: var(--text-body, .9375rem) / var(--text-body-lh, 1.45rem) var(--font-sans);
}

/* Срок рядом с пометкой. Отдельной строкой и заметнее причины: на показе
   первым делом спрашивают «а когда», и ответ не должен теряться в объяснении.
   Цветом не выделяется — «не в этом этапе» это граница, а не ошибка, и
   красный здесь читался бы как поломка. */
.ui-pending__when,
.ui-pending-block__when {
  margin: 12px 0 0;
  padding: 6px 10px;
  /* Блок шириной по содержимому, а не строчный: строчная пометка встала в
     один ряд с кнопкой «Закрыть» и читалась как вторая кнопка. */
  display: block;
  width: fit-content;
  color: var(--text);
  background: var(--surface-3, var(--surface-2));
  border: 1px solid var(--border-card, var(--divider));
  border-radius: var(--radius-pill, 999px);
  font: var(--text-small, .8125rem) / var(--text-small-lh, 1.125rem) var(--font-sans);
}

.ui-pending__when {
  margin: 0 0 16px;
}

.ui-pending-block {
  margin: 24px;
  padding: 20px;
  background: var(--surface-2);
  border: 1px solid var(--border-card, var(--divider));
  border-radius: var(--radius-card, 14px);
}

.ui-pending-block__title {
  margin: 0 0 8px;
  color: var(--text);
  font: var(--text-heading-fw, 600) var(--text-heading, 1.125rem)
        / var(--text-heading-lh, 1.5rem) var(--font-sans);
}

.ui-pending-block__why {
  margin: 0;
  color: var(--text-muted);
  font: var(--text-body, .9375rem) / var(--text-body-lh, 1.45rem) var(--font-sans);
}

/* ------------------------------------------------------------------
   Раздел 0 макета: экраны доступа. Оболочки у них нет — своя раскладка
   по центру страницы.

   Размеры сняты с выгрузки макета 1:1, экран «0.1.1 Вход», узел
   2066:80711, кадр 1440×806 (✓снимок 1:1). Узлом снять не удалось:
   Figma держала ограничение частоты, поэтому при расхождении с узлами
   правда за узлами.

   Числа: карточка 384×542 по центру, внутренний отступ 32, поле 320×42,
   кнопка 320×42, плитка входа 100×84 с зазором 10.

   Цвета макета, которых в каноне нет, взяты ближайшими существующими
   именами, а сами значения вынесены в «Требует решения CTO»
   (docs/design-map-stage1.md):
     карточка   #1D212D -> --surface-2 (#191e29)
     поле       #171A24 -> --field     (#10141d), совпадает по роли
     кнопка     #565A6C -> --surface-4 (#323745), это неактивная кнопка
     плитка     #272A38 -> --surface-3 (#272d3a), почти точное совпадение
   ------------------------------------------------------------------ */
.access {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  padding: 40px 16px;
  background: var(--bg);
  box-sizing: border-box;
}

/* Имя продукта над карточкой. В макете Manrope 800 35/35; начертаний
   Manrope в сборке нет, поэтому наберётся Inter — расхождение известное. */
.access__brand {
  margin: 0 0 31px;
  font: 800 35px/35px var(--font-display);
  letter-spacing: -.02em;
  color: var(--text);
}

.access__card {
  box-sizing: border-box;
  width: 384px;
  max-width: 100%;
  padding: 32px;
  background: var(--surface-2);
  border-radius: var(--radius-2xl);
}

.access__title {
  margin: 0 0 28px;
  font: 600 24px/24px var(--font-sans);
  letter-spacing: var(--text-title-ls);
  color: var(--text);
}

.access__card .ui-notice {
  margin: 0 0 16px;
  border-radius: var(--radius-md);
}

.access__card .ui-form { display: block; }

/* Подпись, поле, ошибка. Ритм макета: 18 на подпись, 6 до поля,
   26 от поля до следующей подписи. */
.access__card .ui-field { display: block; margin: 0 0 26px; }

.access__card .ui-field__label {
  display: block;
  margin: 0 0 6px;
  font: 500 13px/18px var(--font-sans);
  color: var(--text-muted);
}

.access__box { position: relative; display: block; }

.access__card .ui-field__input {
  box-sizing: border-box;
  display: block;
  width: 100%;
  height: 42px;
  padding: 0 14px;
  background: var(--field);
  border: 0;
  border-radius: var(--radius-xl);
  font: 400 15px/18px var(--font-sans);
  color: var(--text);
}

/* Поле с показом пароля: место под кнопку справа, чтобы текст под неё
   не заезжал. */
.access__box--reveal .ui-field__input { padding-right: 44px; }

.access__card .ui-field__input::placeholder { color: var(--text-muted); }

.access__reveal {
  position: absolute;
  top: 50%;
  right: 8px;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  margin-top: -14px;
  padding: 0;
  color: var(--text-muted);
  background: transparent;
  border: 0;
  border-radius: var(--radius-md);
  cursor: pointer;
}

.access__reveal:hover { color: var(--text); }
.access__reveal svg { width: 18px; height: 18px; }

.access__card .ui-field__error {
  margin: 4px 0 0;
  color: var(--danger);
}

/* «Забыли пароль?» — по правому краю, 28 сверху и 14 до кнопки. */
.access__forgot {
  display: block;
  margin: 2px 0 14px;
  font: 400 12px/16px var(--font-sans);
  color: var(--text-muted);
  text-align: right;
  text-decoration: none;
}

.access__forgot:hover { color: var(--text); }

.access__card .ui-form__error { margin: 0 0 8px; }

.access__submit {
  box-sizing: border-box;
  display: block;
  width: 100%;
  height: 42px;
  padding: 0;
  color: var(--text);
  background: var(--surface-4);
  border: 0;
  border-radius: var(--radius-pill);
  font: 500 15px/18px var(--font-sans);
  cursor: pointer;
}

.access__submit:hover { background: var(--surface-3); }
.access__submit[disabled] { color: var(--text-subtle); cursor: default; }

.access__card .ui-form__state { margin: 8px 0 0; text-align: center; }

/* Покой формы в макете ничем не подписан: строка «Готово к отправке» под
   кнопкой — наша, и на экране она лишняя. Убирается с глаз, но остаётся в
   дереве доступности: это живая область, через которую чтение вслух узнаёт
   об отправке и об отказе. Как только состояние перестаёт быть покоем,
   строка появляется на экране. */
.access__card .ui-form__state[data-state="idle"] {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Разделитель со словами посередине: 24 сверху, 9 до плиток. */
.access__or {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 24px 0 9px;
  font: 400 12px/16px var(--font-sans);
  color: var(--text-muted);
}

.access__or::before,
.access__or::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--hairline);
}

/* Плитки внешнего входа: три по 100×84 с зазором 10 — ровно 320. */
.access__providers {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.access__provider {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  height: 84px;
  padding: 0;
  color: var(--text-muted);
  background: var(--surface-3);
  border: 0;
  border-radius: var(--radius-card-lg);
  cursor: pointer;
}

.access__provider:hover { color: var(--text); }
.access__provider svg { width: 28px; height: 28px; }

/* Знаки поставщиков в макете нарисованы их фирменными цветами. Правило
   клиента запрещает hex, а фирменный цвет чужого сервиса токеном нашей
   палитры быть не может — поэтому здесь знаки одноцветные. Вопрос
   вынесен владельцу. */
.access__provider-name { font: 500 12px/16px var(--font-sans); }

.access__footer {
  margin: 19px 0 0;
  font: 400 12px/16px var(--font-sans);
  color: var(--text-muted);
  text-align: center;
}

.access__footer-link {
  color: var(--text);
  font-weight: 600;
  text-decoration: none;
}

.access__guest {
  margin: 29px 0 0;
  font: 400 12px/16px var(--font-sans);
  color: var(--text-muted);
  text-decoration: none;
}

.access__guest:hover { color: var(--text); }

/* Окна уже карточки: карточка перестаёт быть фиксированной. */
@media (max-width: 420px) {
  .access__card { width: 100%; padding: 24px 20px; }
}

/* Пояснение под заголовком карточки доступа. В макете «0.4.1 Привязка
   номера» это единственная строка Inter 400 13/20 — она объясняет, зачем
   экран, до того как человек тронет поле. */
.access__body {
  margin: 0 0 24px;
  font: 400 13px/20px var(--font-sans);
  color: var(--text-muted);
}

/* ------------------------------------------------------------------
   Раздел 3 макета: свой профиль (экран «3.1 Профиль мой», узел
   2118:257461, кадр 1440×3571).

   Размеры сняты с выгрузки макета 1:1 (✓снимок 1:1). Узлом снять не
   удалось: ключ доступа к макету упёрся в суточную квоту, и до её
   возврата числа держатся на снимке.

   Числа: колонка содержимого 1084 по центру основной области
   (в макете x 212..1296 при основной области 68..1440), обложка во всю
   ширину области высотой 350, шапка профиля 1084×132 заходит на нижний
   край обложки, лицо 124×124 с отступом 8 от края шапки, кнопки высотой
   40 с зазором 10 у правого края с отступом 24.
   ------------------------------------------------------------------ */
/* Фон страницы у профиля свой не нужен: основная область оболочки теперь и
   есть фон. Раньше здесь стояла заливка — заплатка на то, что оболочка красила
   область поверхностью и карточки на ней исчезали. Причина устранена в
   оболочке, заплатка снята. */
.profile {
  display: block;
  min-height: 100%;
}

/* Обложки нет ни в ответе службы, ни в хранилище: место под неё держит
   заливка из токенов. Чужой картинки здесь не будет — правило
   self-contained сильнее желания показать красиво. */
.profile__cover {
  height: 350px;
  background-color: var(--surface-2);
  background-image: var(--grad-aside);
}

/* Колонка содержимого — ровно 1084: боковой отступ здесь сужал бы карточки,
   а в макете шапка занимает всю ширину колонки. Отступ по бокам появляется
   только там, где колонка перестаёт помещаться. */
.profile__wrap {
  box-sizing: border-box;
  width: 1084px;
  max-width: 100%;
  margin: 0 auto;
  padding: 0 0 32px;
}

/* Шапка заходит на обложку: в макете её верх выше нижнего края обложки
   на девять точек. */
.profile__head {
  display: flex;
  align-items: center;
  gap: 16px;
  box-sizing: border-box;
  min-height: 132px;
  margin-top: -9px;
  padding: 4px 24px 4px 8px;
  background: var(--surface);
  border-radius: var(--radius-card);
}

.profile__face {
  display: grid;
  place-items: center;
  flex: none;
  width: 124px;
  height: 124px;
  font: 600 44px/1 var(--font-sans);
  color: var(--on-mint);
  background: var(--grad-avatar, var(--surface-3));
  border-radius: var(--radius-2xl);
}

.profile__who { flex: 1; min-width: 0; }

.profile__name {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 6px;
  font: var(--text-title-fw, 650) var(--text-title) / var(--text-title-lh) var(--font-sans);
  letter-spacing: var(--text-title-ls);
  color: var(--text);
}

/* Адрес стоит плашкой рядом с именем, а не строкой под ним. */
.profile__handle {
  padding: 3px 10px;
  font: 500 var(--text-caption) / var(--text-caption-lh) var(--font-sans);
  color: var(--text-muted);
  background: var(--surface-2);
  border-radius: var(--radius-pill);
}

.profile__bio {
  margin: 0 0 6px;
  max-width: 560px;
  font: var(--text-label) / var(--text-label-lh) var(--font-sans);
  color: var(--text-muted);
}

.profile__status {
  margin: 0;
  font: var(--text-caption) / var(--text-caption-lh) var(--font-sans);
  color: var(--text-subtle);
}

.profile__acts {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: none;
}

.profile__act {
  height: 40px;
  padding: 0 16px;
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid transparent;
  border-radius: var(--radius-nav);
  font: 500 var(--text-label) / 1 var(--font-sans);
  cursor: pointer;
  white-space: nowrap;
}

.profile__act:hover { background: var(--surface-3); }

/* Выделенное действие в макете обведено, а не залито. */
.profile__act--strong { border-color: var(--hairline-strong); }

/* Две колонки под шапкой: в макете 756 слева, зазор 24, 304 справа —
   вместе те же 1084, что и у шапки. */
.profile__body {
  display: grid;
  grid-template-columns: 756px 304px;
  gap: 24px;
  margin-top: 24px;
  align-items: start;
}

.profile__main { display: grid; gap: 24px; min-width: 0; }

/* Правая колонка: карточки 304 с промежутком 20 и внутренним отступом 24. */
.profile__side { display: grid; gap: 20px; min-width: 0; }

.profile__side-card {
  padding: 24px;
  background: var(--surface);
  border-radius: var(--radius-card);
}

.profile__side-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 16px;
}

.profile__side-title {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin: 0;
  font: var(--text-heading-fw, 600) var(--text-heading) / var(--text-heading-lh)
        var(--font-sans);
  color: var(--text);
}

/* Число рядом с названием стоит верхним индексом — как в макете. */
.profile__side-count {
  align-self: flex-start;
  font: 500 var(--text-caption) / 1 var(--font-sans);
  color: var(--text-muted);
}

.profile__side-tail {
  font: var(--text-caption) / var(--text-caption-lh) var(--font-sans);
  color: var(--text-muted);
  white-space: nowrap;
}

.profile__side-note {
  margin: 0;
  font: var(--text-label) / var(--text-label-lh) var(--font-sans);
  color: var(--text-subtle);
}

.profile__card {
  padding: 20px 24px;
  background: var(--surface);
  border-radius: var(--radius-card);
}

.profile__card-title {
  margin: 0 0 16px;
  font: var(--text-heading-fw, 600) var(--text-heading) / var(--text-heading-lh)
        var(--font-sans);
  color: var(--text);
}

.profile__card .ui-field { display: block; margin: 0 0 16px; }

.profile__card .ui-field__label {
  display: block;
  margin: 0 0 6px;
  font: 500 var(--text-label) / var(--text-label-lh) var(--font-sans);
  color: var(--text-muted);
}

.profile__card .ui-field__input {
  box-sizing: border-box;
  width: 100%;
  max-width: 420px;
  height: 42px;
  padding: 0 14px;
  background: var(--field);
  border: 0;
  border-radius: var(--radius-xl);
  font: var(--text-body) / 1 var(--font-sans);
  color: var(--text);
}

.profile__card .access__submit { max-width: 420px; }
.profile__card .ui-form { display: block; }

/* Узкие окна: шапка складывается в столбец, кнопки уходят под имя. */
@media (max-width: 1100px) {
  .profile__wrap { padding: 0 16px 32px; }
  .profile__body { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 900px) {
  .profile__head { flex-direction: column; align-items: flex-start; }
  .profile__acts { flex-wrap: wrap; }
}

/* Левая колонка профиля: карточка игр и раздел заметок (макет 3.1,
   ✓снимок 1:1). Числа: карточки шириной 756 с промежутком 20, внутренний
   отступ 24 по бокам и 16 сверху и снизу у раздела заметок; строка вкладок 20;
   строка создания записи 40, лицо в ней 34; поле создания скруглено капсулой,
   справа от него зазор 10. */
.profile__tabs {
  display: flex;
  align-items: baseline;
  gap: 16px;
  margin-bottom: 16px;
}

.profile__tab {
  font: var(--text-heading-fw, 600) var(--text-heading) / var(--text-heading-lh)
        var(--font-sans);
  color: var(--text-subtle);
}

.profile__tab.is-on { color: var(--text); }

/* Пустая полка под игру: в макете это пунктирный прямоугольник с плюсом,
   а не кнопка с заливкой. */
.profile__add {
  display: grid;
  place-items: center;
  gap: 10px;
  width: 100%;
  min-height: 176px;
  padding: 24px;
  color: var(--text-muted);
  background: transparent;
  border: 1px dashed var(--hairline-strong);
  border-radius: var(--radius-2xl);
  font: var(--text-label) / var(--text-label-lh) var(--font-sans);
  cursor: pointer;
}

.profile__add:hover { color: var(--text); }
.profile__add-mark { font: 400 20px/1 var(--font-sans); }

.profile__notes { padding: 16px 24px; }

.profile__compose {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
}

.profile__compose-face {
  flex: none;
  width: 34px;
  height: 34px;
  background: var(--grad-avatar, var(--surface-3));
  border-radius: var(--radius-pill);
}

/* Поле создания записи — ссылка, а не поле ввода: набор идёт на своём экране,
   а поле, которое выглядит полем и не принимает текст, обманывает. */
.profile__compose-field {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  flex: 1;
  min-width: 0;
  height: 40px;
  padding: 0 16px;
  color: var(--text-muted);
  background: var(--surface-3);
  border-radius: var(--radius-pill);
  font: var(--text-label) / 1 var(--font-sans);
  text-decoration: none;
}

.profile__compose-field:hover { color: var(--text); }

/* ------------------------------------------------------------------
   Переписки (кадр «личный чат», узел 2066:184664, 1440×1098,
   ✓снимок 1:1).

   Числа: панель ленты значков и колонки раздела 5..372 без зазора между
   ними (73 + 295), зазор до основной области 5, отступ страницы 5.
   Шапка переписки поверхностью высотой 80, строка ввода поверхностью
   высотой 48, между ними лента сообщений на фоне страницы.
   ------------------------------------------------------------------ */
.talks__side {
  display: flex;
  flex-direction: column;
  min-height: 0;
  height: 100%;
}

/* Вкладки колонки: подчёркивание у выбранной, а не заливка. */
.talks__tabs {
  display: flex;
  gap: 4px;
  padding: 0 12px;
  border-bottom: 1px solid var(--hairline);
  overflow-x: auto;
  scrollbar-width: none;
}

.talks__tab {
  flex: none;
  white-space: nowrap;
  padding: 12px 10px;
  color: var(--text-muted);
  font: 500 var(--text-label) / 1 var(--font-sans);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}

.talks__tab[data-active="yes"] {
  color: var(--text);
  border-bottom-color: var(--color-accent);
}

/* Поиск по чатам. Поле видно и набирается, но отбора не делает: операции
   поиска по переписке в контракте нет. Названо в журнале вёрстки. */
.talks__search { display: block; padding: 12px; }

.talks__search-label {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.talks__search-input {
  box-sizing: border-box;
  width: 100%;
  height: 36px;
  padding: 0 14px;
  color: var(--text);
  background: var(--field);
  border: 0;
  border-radius: var(--radius-pill);
  font: var(--text-label) / 1 var(--font-sans);
}

.talks__search-input::placeholder { color: var(--text-subtle); }

/* Список диалогов занимает остаток колонки и крутится сам. */
.talks__side .ui-list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 0 8px 8px;
}

.talks__side .ui-list [data-items] {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Строка списка: высота 74, лицо 56, отступ 12 — числа со снимка 1:1. */
.talks__row { border-radius: var(--radius-lg); }

.talks__row[data-selected="yes"] { background: var(--nav-active-bg, var(--surface-2)); }
.talks__row:hover { background: var(--nav-hover-bg, var(--surface-2)); }

.talks__row-link {
  display: flex;
  align-items: center;
  gap: 12px;
  box-sizing: border-box;
  height: 74px;
  padding: 0 12px;
  /* Цвет задан, хотя все дети красятся сами: незаданный цвет ссылки — это
     браузерная синева, ждущая первого текста без своего правила. Находка
     обхода, и видна она только на широком окне: на узком колонка списка
     скрыта, и ссылок на экране нет вовсе. */
  color: var(--text);
  text-decoration: none;
}

.talks__row-face {
  display: grid;
  place-items: center;
  flex: none;
  width: 56px;
  height: 56px;
  color: var(--on-mint);
  background: var(--grad-avatar, var(--surface-3));
  border-radius: var(--radius-pill);
  font: 600 20px/1 var(--font-sans);
}

.talks__row-body { display: grid; gap: 2px; min-width: 0; }

.talks__row-name {
  color: var(--text);
  font: 500 var(--text-label) / var(--text-label-lh) var(--font-sans);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.talks__row-note {
  color: var(--text-muted);
  font: var(--text-caption) / var(--text-caption-lh) var(--font-sans);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Область переписки. */
/* Область занимает всю высоту: строка ввода в макете прижата к низу, а не
   идёт следом за последним сообщением. Нужна именно высота, а не минимальная:
   с минимальной вложенная область не получает определённой высоты и лента не
   растягивается. */
.talks {
  display: flex;
  flex-direction: column;
  height: 100%;
}

.talks__empty {
  margin: auto;
  color: var(--text-muted);
  font: var(--text-body) / var(--text-body-lh) var(--font-sans);
}

/* Область переписки (кадр «личный чат», ✓снимок 1:1).
   Числа: шапка поверхностью высотой 80 с отступом 16 и лицом 56; лента
   сообщений на фоне страницы; строка ввода поверхностью высотой 48.
   Пузыри: входящий #1C212E -> --surface-3, исходящий тёмно-бирюзовый
   #325F65 -> --color-brand (точного токена нет, значение вынесено в
   «Требует решения CTO»). */
.talks__pane {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

.talks__head {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: none;
  box-sizing: border-box;
  height: 80px;
  padding: 0 16px;
  background: var(--surface);
  border-radius: var(--radius-card);
}

.talks__face {
  flex: none;
  width: 56px;
  height: 56px;
  background: var(--grad-avatar, var(--surface-3));
  border-radius: var(--radius-pill);
}

.talks__who { flex: 1; min-width: 0; }

.talks__name {
  margin: 0;
  font: var(--text-heading-fw, 600) var(--text-heading) / var(--text-heading-lh)
        var(--font-sans);
  color: var(--text);
}

/* Состояние переписки — подписью под именем, как «печатает …» в макете. */
.talks__state {
  display: flex;
  gap: 10px;
  margin: 2px 0 0;
  font: var(--text-caption) / var(--text-caption-lh) var(--font-sans);
  color: var(--text-muted);
}

.talks__acts { display: flex; align-items: center; gap: 8px; flex: none; }

.talks__act {
  height: 32px;
  padding: 0 12px;
  color: var(--text-muted);
  background: var(--surface-2);
  border: 0;
  border-radius: var(--radius-nav);
  font: var(--text-caption) / 1 var(--font-sans);
  cursor: pointer;
}

.talks__act:hover { color: var(--text); }

.talks__icon {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  padding: 0;
  color: var(--text-muted);
  background: transparent;
  border: 0;
  border-radius: var(--radius-nav);
  cursor: pointer;
  flex: none;
}

.talks__icon:hover { color: var(--text); background: var(--nav-hover-bg, var(--surface-2)); }
.talks__icon svg { width: 22px; height: 22px; }

/* Лента сообщений лежит на фоне страницы, а не на поверхности. */
.talks__feed {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 16px 16px 8px;
}

.talks__feed [data-items] {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Пузырь. Свой прижат вправо и окрашен, чужой — влево. */
.talks__feed [data-items] li {
  max-width: 480px;
  padding: 10px 14px;
  background: var(--surface-3);
  border-radius: var(--radius-2xl);
  color: var(--text);
  font: var(--text-body) / var(--text-body-lh) var(--font-sans);
  align-self: flex-start;
}

.talks__feed [data-items] li[data-mine="yes"] {
  align-self: flex-end;
  background: var(--color-brand);
}

/* Неподтверждённое сообщение видно как неподтверждённое, а не как обычное. */
.talks__feed [data-items] li[data-draft="yes"] { opacity: .6; }

/* Позвали меня. Пометка над текстом, а не рядом: она про всю реплику, и
   слипшись с текстом читается как его часть. */
.talks__feed [data-items] li[data-mentions-me="yes"] { border-left: 2px solid var(--color-accent); }
.talks__feed [data-items] [data-mentioned] {
  display: block;
  color: var(--accent-text);
  font-size: 12px;
}

/* Обрезанный текст: пометка приглушена — это про показ, а не про смысл. */
.talks__feed [data-items] [data-cut] { color: var(--text-muted); font-size: 12px; }

/* Кнопка снятия у реплики: тише текста — это работа разбирающего, а не
   часть разговора. Видна только тому, у кого есть право. */
.talks__remove {
  margin-left: 8px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--text-muted);
  font-size: 12px;
  cursor: pointer;
}

/* След снятия: три строки, а не одна фраза. Пометка сверху, под ней причина
   и кто снял — так же, как читается карточка разбора. */
.talks__feed [data-removed] > span { display: block; }
.talks__feed [data-removed-mark] { color: var(--text); }
.talks__feed [data-removed-why],
.talks__feed [data-removed-by] { font-size: 12px; }

/* Жалоба на запись: действие редкое, и голос у него тише. */
.post__report { margin-left: auto; color: var(--text-muted); font-size: 13px; }

/* Снятая реплика. Приглушена и без заливки: она уже не разговор, а след от
   него, и выглядеть как обычное сообщение не должна. Причина остаётся
   читаемой — с ней человек и спорит. */
.talks__feed [data-items] li:has([data-removed]) {
  background: none;
  border: 1px dashed var(--border-card);
  color: var(--text-muted);
}

.talks__composer {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: none;
  flex-wrap: wrap;
  box-sizing: border-box;
  min-height: 48px;
  padding: 4px 8px;
  margin-top: var(--shell-gap, 5px);
  background: var(--surface);
  border-radius: var(--radius-card);
}

.talks__field { display: block; flex: 1; min-width: 0; margin: 0; }

.talks__field-label {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.talks__composer .talks__input {
  box-sizing: border-box;
  width: 100%;
  height: 40px;
  padding: 0 14px;
  color: var(--text);
  background: var(--field);
  border: 0;
  border-radius: var(--radius-pill);
  font: var(--text-body) / 1 var(--font-sans);
}

.talks__composer .talks__input::placeholder { color: var(--text-subtle); }

.talks__send {
  flex: none;
  height: 40px;
  padding: 0 18px;
  color: var(--on-mint);
  background: var(--color-accent);
  border: 0;
  border-radius: var(--radius-pill);
  font: 600 var(--text-label) / 1 var(--font-sans);
  cursor: pointer;
}

.talks__send:hover { background: var(--color-accent-hover); }

/* Ошибка и состояние отправки занимают всю ширину под строкой. */
.talks__composer .ui-form__error,
.talks__composer .ui-form__state { flex-basis: 100%; margin: 0 0 0 8px; }
.talks__composer .ui-form__state[data-state="idle"] {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Люди: поиск, найденные, заявки, чёрный список.
   Своего кадра у экрана в макете нет — числа взяты из системы, снятой с
   измеренных экранов: карточка с отступом 24, строка 74 с лицом 56 и
   отступом 12. По своему кадру не сверено, см. журнал вёрстки. */
.people { display: block; min-height: 100%; }

.people__wrap {
  box-sizing: border-box;
  width: 756px;
  max-width: 100%;
  margin: 0 auto;
  padding: 24px 0 32px;
  display: grid;
  gap: 20px;
}

.people__title {
  margin: 0;
  font: var(--text-title-fw, 650) var(--text-title) / var(--text-title-lh) var(--font-sans);
  letter-spacing: var(--text-title-ls);
  color: var(--text);
}

.people__search { display: flex; gap: 10px; align-items: center; }
.people__search-box { display: block; flex: 1; min-width: 0; margin: 0; }

.people__search-label {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.people__search .people__search-input {
  box-sizing: border-box;
  width: 100%;
  height: 42px;
  padding: 0 16px;
  color: var(--text);
  background: var(--field);
  border: 0;
  border-radius: var(--radius-pill);
  font: var(--text-body) / 1 var(--font-sans);
}

.people__search-go {
  flex: none;
  height: 42px;
  padding: 0 20px;
  color: var(--on-mint);
  background: var(--color-accent);
  border: 0;
  border-radius: var(--radius-pill);
  font: 600 var(--text-label) / 1 var(--font-sans);
  cursor: pointer;
}

.people__search-go:hover { background: var(--color-accent-hover); }

/* Карточка списка — числа СНЯТЫ с кадра «3.3.1 Заявки в друзья», а не
   применены с других экранов, как было до сверки: отступы 30 сверху,
   40 справа, 30 снизу и слева, заливка #151B28 — это `--surface`.
   Скругление кадра — 40; в наборе токенов такого значения нет, ближайшее
   `--radius-card` = 25px. Оставлено токеном, расхождение записано
   в открытые вопросы: свои числа в стиль не вписываю. */
.people__card {
  padding: 30px 40px 30px 30px;
  background: var(--surface);
  border-radius: var(--radius-card);
}

.people__card-title {
  margin: 0 0 12px;
  font: var(--text-heading-fw, 600) var(--text-heading) / var(--text-heading-lh)
        var(--font-sans);
  color: var(--text);
}

/* Промежуток между строками с кадра СНЯТЬ НЕ УДАЛОСЬ: до нужного узла не
   дошёл, а API отвечает ограничением частоты. Взят измеренный промежуток
   ЭТОЙ ЖЕ карточки — 16, — то есть число из того же кадра, а не выдуманное.
   Это отмечено и здесь, и в журнале: строка не выдаётся за сверенную. */
.people__rows {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 16px;
}

/* Строка — это КАРТОЧКА, а не пункт списка с чертой снизу.

   Так в кадре: своя рамка #1F2739 (`--border-card`) толщиной 1, скругление
   20, высота 93, отступы 14 сверху, 15 снизу, 9 слева, справа 0, а
   содержимое разнесено по краям («Auto» в макете — это space-between).

   До сверки здесь стояла высота 74 и черта снизу: числа были применены с
   ДРУГИХ измеренных экранов, и это честно значилось в журнале как
   «по своему кадру не сверено». Теперь сверено, и разошлось.

   Скругление 20 в наборе токенов отсутствует; взято ближайшее
   `--radius-2xl` (18px). Расхождение в два пиксела записано в открытые
   вопросы — вписать 20px сюда значило бы завести цвет мимо токенов. */
.people__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 93px;
  padding: 14px 0 15px 9px;
  background: var(--surface);
  border: 1px solid var(--border-card);
  border-radius: var(--radius-2xl);
}

.people__face {
  display: grid;
  place-items: center;
  flex: none;
  width: 56px;
  height: 56px;
  color: var(--on-mint);
  background: var(--grad-avatar, var(--surface-3));
  border-radius: var(--radius-pill);
  font: 600 20px/1 var(--font-sans);
}

/* Кружок без буквы: имени человека в этой строке нет. */
.people__face--none { background: var(--surface-3); }

.people__body { display: grid; gap: 2px; flex: 1; min-width: 0; }

.people__name {
  color: var(--text);
  font: 500 var(--text-label) / var(--text-label-lh) var(--font-sans);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.people__note {
  color: var(--text-muted);
  font: var(--text-caption) / var(--text-caption-lh) var(--font-sans);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.people__acts { display: flex; gap: 8px; flex: none; }

.people__act {
  height: 34px;
  padding: 0 14px;
  color: var(--text);
  background: var(--surface-2);
  border: 0;
  border-radius: var(--radius-nav);
  font: 500 var(--text-caption) / 1 var(--font-sans);
  cursor: pointer;
  white-space: nowrap;
}

.people__act:hover { background: var(--surface-3); }

.people__act--go { color: var(--on-mint); background: var(--color-accent); }
.people__act--go:hover { background: var(--color-accent-hover); }

@media (max-width: 820px) {
  .people__wrap { padding: 24px 16px 32px; }
}

/* ------------------------------------------------------------------
   Общий каркас простой страницы: заголовок, карточки, строки списка.
   Своих кадров у этих экранов в макете «Этапа 1» нет — числа взяты из
   системы, снятой с измеренных экранов (карточка с отступом 24 и
   скруглением карточки, колонка содержимого 756). По макету не сверено.
   ------------------------------------------------------------------ */
.page { display: block; min-height: 100%; }

.page__wrap {
  box-sizing: border-box;
  width: 756px;
  max-width: 100%;
  margin: 0 auto;
  padding: 24px 0 32px;
  display: grid;
  gap: 16px;
}

.page__title {
  margin: 0;
  font: var(--text-title-fw, 650) var(--text-title) / var(--text-title-lh) var(--font-sans);
  letter-spacing: var(--text-title-ls);
  color: var(--text);
}

.page__card {
  padding: 20px 24px;
  background: var(--surface);
  border-radius: var(--radius-card);
  display: grid;
  gap: 12px;
}

.page__note {
  margin: 0;
  color: var(--text-muted);
  font: var(--text-caption) / var(--text-caption-lh) var(--font-sans);
}

.page__note:empty { display: none; }

.page__label {
  color: var(--text-muted);
  font: 500 var(--text-label) / var(--text-label-lh) var(--font-sans);
}

.page__rows { list-style: none; margin: 0; padding: 0; }

.page__row {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 56px;
  padding: 8px 0;
  border-bottom: 1px solid var(--hairline);
}

.page__row:last-child { border-bottom: 0; }

/* Метка непрочитанного. Прочитанное её не теряет, а гасит: исчезающая метка
   меняет ширину строки, и список дёргается при отметке. */
.page__row-dot {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--color-notify, var(--color-mint));
}

.page__row[data-read="true"] .page__row-dot { background: var(--surface-3); }

.page__row-body { display: grid; gap: 2px; min-width: 0; }

.page__row-name {
  color: var(--text);
  font: 500 var(--text-label) / var(--text-label-lh) var(--font-sans);
  text-decoration: none;
}

a.page__row-name:hover { color: var(--accent-text); }

.page__row-note {
  color: var(--text-subtle);
  font: var(--text-caption) / var(--text-caption-lh) var(--font-sans);
  overflow-wrap: anywhere;
}

.page__text {
  box-sizing: border-box;
  width: 100%;
  min-height: 132px;
  padding: 12px 14px;
  color: var(--text);
  background: var(--field);
  border: 0;
  border-radius: var(--radius-xl);
  font: var(--text-body) / var(--text-body-lh) var(--font-sans);
  resize: vertical;
}

.page__text::placeholder { color: var(--text-subtle); }

.page__upload {
  display: grid;
  gap: 8px;
  padding: 16px;
  background: var(--surface-2);
  border-radius: var(--radius-xl);
}

.page__file { color: var(--text-muted); font: var(--text-caption) / 1.4 var(--font-sans); }
.page__progress { width: 100%; height: 6px; }
.page__upload-acts { display: flex; gap: 8px; }

/* В покое полосы и кнопок отмены нет: пустая полоса на весь блок читается
   как незавершённая загрузка, которой не было. Они появляются, когда
   загрузка идёт, и остаются, пока её можно отменить или повторить. */
.page__upload[data-stage="idle"] .page__progress,
.page__upload[data-stage="idle"] .page__upload-acts,
.page__upload[data-stage="ready"] .page__progress { display: none; }
.page__preview:empty { display: none; }
.page__preview img { max-width: 100%; border-radius: var(--radius-lg); }

.page__act {
  height: 34px;
  padding: 0 14px;
  color: var(--text);
  background: var(--surface-3);
  border: 0;
  border-radius: var(--radius-nav);
  font: 500 var(--text-caption) / 1 var(--font-sans);
  cursor: pointer;
}

.page__go {
  justify-self: start;
  height: 42px;
  padding: 0 22px;
  color: var(--on-mint);
  background: var(--color-accent);
  border: 0;
  border-radius: var(--radius-pill);
  font: 600 var(--text-label) / 1 var(--font-sans);
  cursor: pointer;
}

.page__go:hover { background: var(--color-accent-hover); }

@media (max-width: 820px) {
  .page__wrap { padding: 24px 16px 32px; }
}

/* Переключатель приватности: подпись словами, машинный ключ — мелко под ней
   и только у ключей, которых мы не знаем. */
.profile__privacy {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 8px 0;
}

.profile__privacy-body { display: grid; gap: 2px; }

.profile__privacy-name {
  color: var(--text);
  font: var(--text-label) / var(--text-label-lh) var(--font-sans);
}

/* Выбор в настройках приватности («кто может писать»). Правило тёмного поля
   живёт внутри `.ui-screen` и сюда не достаёт: профиль собран другими
   классами. Без своего фона выбор белеет от браузера — то самое светлое
   пятно посреди тёмной карточки, четвёртый случай за ночь. */
.profile__privacy select {
  flex: none;
  box-sizing: border-box;
  padding: 4px 8px;
  color: var(--text);
  background: var(--field);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-btn, 8px);
  font: var(--text-label) / var(--text-label-lh) var(--font-sans);
}



/* Машинное значение, показанное нарочно.
   
   Класс несёт смысл, а не только вид: обход экранов (`screens.html?sweep`)
   пропускает текст внутри `is-machine` и ругается на машинную строку везде
   ещё. То есть правило звучит так: код, опознаватель или ключ на экране
   допустимы — но только помеченными как код, а не выданными за фразу.
   Снял пометку — обход об этом скажет. */
.is-machine {
  color: var(--text-subtle);
  font: var(--text-caption) / var(--text-caption-lh) var(--font-mono);
  overflow-wrap: anywhere;
}

/* Своя жалоба: номер, код причины, исход. Код причины показан кодом —
   моноширинным и приглушённым, а не строкой на человеческом языке: название
   причины живёт в справочнике, который приходит с формой жалобы, и в списке
   своих жалоб его нет. */
.ui-screen [data-list="reports"] [data-items] li {
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
}

.report__number {
  color: var(--text);
  font: 500 var(--text-label) / var(--text-label-lh) var(--font-sans);
}

[data-report-outcome],
[data-report-pending] {
  margin-left: auto;
  color: var(--text-muted);
  font: var(--text-caption) / var(--text-caption-lh) var(--font-sans);
}

/* Узкое окно и строка ввода сообщения.
   
   Блок стоит в КОНЦЕ файла нарочно: базовое правило `.talks__field { flex: 1 }`
   описано ниже ранних медиазапросов, а `flex` — сокращение, оно задаёт и
   основу. Тот же селектор, объявленный выше по файлу, проигрывает по порядку —
   на этом я и попался: правило было верным, но не выигрывало.
   
   На 400 точках поле сжималось до 139 при кнопке отправки в 101: писать в
   такое поле нельзя. Поле уходит на свою строку целиком, вложение и отправка
   встают под ним. */
@media (max-width: 620px) {
  .talks__field { order: -1; flex-basis: 100%; flex-grow: 1; }
  .talks__composer { padding: 8px; }
}

/* Узкое окно и строка человека.
   
   На 400 точках имя сжималось до НУЛЯ: лицо и две кнопки занимали всю строку,
   и на экране оставался человек без имени. Перелива при этом не было — обход
   молчал, поймано глазами.
   
   Действия уходят на свою строку под именем. Высота перестаёт быть
   фиксированной: две строки в 74 не помещаются. */
@media (max-width: 620px) {
  .people__row {
    flex-wrap: wrap;
    height: auto;
    padding: 12px 0;
  }

  .people__body { flex-basis: calc(100% - 68px); }
  .people__acts { flex-basis: 100%; margin-left: 68px; }
}

/* Узкое окно и шапка переписки. Имя сжималось до 19 точек: лицо, кнопка
   «Прочитано» и два знака занимали строку целиком. Действия уходят под имя —
   прятать их нельзя, отметка о прочтении и звонки это отдельные действия. */
@media (max-width: 620px) {
  .talks__head {
    height: auto;
    min-height: 80px;
    flex-wrap: wrap;
    padding: 8px 12px;
  }

  .talks__who { flex-basis: calc(100% - 68px); }
  .talks__acts { flex-basis: 100%; justify-content: flex-end; }
}

/* «Дальше» на экране итога — та же кнопка, но ссылкой: путь продолжается, и
   выглядеть это должно как продолжение, а не как примечание. */
.access__submit--link {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 12px;
  text-decoration: none;
}

/* Лента открытых записей. Своего кадра в сырье нет — вид по общей системе:
   карточка с отступом 24 и скруглением карточки, промежуток 16. */
.feed__items { display: grid; gap: 16px; }

.feed__card {
  padding: 20px 24px;
  background: var(--surface);
  border-radius: var(--radius-card);
}

/* Шапка карточки по кадру 1.1: лицо слева, автор и время рядом с ним.
   Размеры взяты из канона, а не с кадра: числа кадра снять не удалось, и это
   отмечено в журнале. */
.feed__top {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 10px;
}

/* Кружок без буквы: имени у нас нет, есть опознаватель. */
.feed__face {
  flex: none;
  width: 40px;
  height: 40px;
  background: var(--surface-3);
  border-radius: var(--radius-pill);
}

.feed__by { display: grid; gap: 2px; min-width: 0; }

.feed__title {
  display: block;
  margin: 0 0 6px;
  color: var(--text);
  font: var(--text-title-fw, 650) var(--text-title) / var(--text-title-lh)
        var(--font-sans);
  text-decoration: none;
}

.feed__title:hover { text-decoration: underline; }

/* Текст обрезан тремя строками: в кадре запись в ленте показана врезкой, а
   дочитать зовёт «Читать далее». Лента, показывающая запись целиком, — это
   уже не лента. */
.feed__text {
  margin: 0 0 8px;
  color: var(--text-muted);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.feed__more { color: var(--accent-text); }

.feed__marks {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin: 12px 0 0;
  padding-top: 12px;
  border-top: 1px solid var(--hairline);
  color: var(--text-muted);
  font: var(--text-caption) / var(--text-caption-lh) var(--font-sans);
}

.feed__who {
  display: flex;
  align-items: baseline;
  gap: 4px;
  color: var(--text-muted);
  font: var(--text-caption) / var(--text-caption-lh) var(--font-sans);
  min-width: 0;
}

/* Опознаватель автора обрывается многоточием. Целиком он в тридцать шесть
   знаков и на узком экране разворачивался в три строки над каждой записью —
   шум там, где содержание это текст записи.
   
   Обрыв здесь честен: значение и так показано КАК опознаватель, а не как имя,
   и полностью остаётся в разметке — скопировать можно. Обрывать так имя
   человека было бы нельзя. */
.feed__who .is-machine {
  max-width: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.feed__when {
  flex: none;
  color: var(--text-subtle);
  font: var(--text-caption) / var(--text-caption-lh) var(--font-sans);
}

.feed__text {
  margin: 0;
  color: var(--text);
  font: var(--text-body) / var(--text-body-lh) var(--font-sans);
}

/* Текст записи в ленте — ссылка на запись. Подчёркивания нет: подчёркнутый
   абзац читается хуже, а то, что он ведёт куда-то, видно по наведению. */
a.feed__text { display: block; text-decoration: none; }
a.feed__text:hover { color: var(--accent-text); }

/* Подпись, скрытая от глаз, но не от чтения вслух. Раньше такие места
   повторяли одни и те же семь строк в четырёх местах — теперь имя одно. */
.ui-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Кнопки без класса. Во внутреннем контуре кнопки написаны голыми тегами, без
   `ui-button`, — и получали от браузера светлую заливку: белые прямоугольники
   посреди тёмной карточки. Та же болезнь, что была у полей, и то же лекарство:
   правило по тегу, а не по классу, потому что классов там нет.
   
   Кнопки с классом правило не трогает — у них свой вид. */
.ui-screen button:not([class]) {
  padding: 8px 14px;
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  font: inherit;
  cursor: pointer;
}

.ui-screen button:not([class]):hover { background: var(--surface-3); }

/* Согласия на регистрации: флажок и текст одной строкой, как в кадре 0.2.1.
   Текст мельче и приглушён — это условие, а не поле ввода. */
/* Вопрос перед необратимым: объяснение сверху, ответы под ним. Не окно —
   окна в клиенте нет, а заводить его ради одного вопроса значит завести
   вторую систему поверх той, что уже есть. */
.ui-confirm {
  display: grid;
  gap: 8px;
  padding: 12px;
  border: 1px solid var(--danger);
  border-radius: var(--radius-lg);
  background: var(--danger-soft);
}

/* Подпись согласия. То, под чем ставят отметку, читается наравне с остальным
   текстом, а не мельче всего на экране. */
.ui-consent__take { display: block; color: var(--text); font-size: 13px; }

/* Необратимое действие: рамка и цвет предупреждения вместо заливки акцентом.
   Заливка делает кнопку главной на экране — а главное здесь не удалить, а
   понять последствия. */
/* Селектор нарочно точнее правила главных действий.

   Кнопку красит перечень «главных» по имени действия — `submit`, `publish`,
   `request` и прочие, — и «запросить удаление» попало в него наравне с
   «запросить архив». Два класса этот перечень не перебивали: у него выше
   точность. Найдено измерением совпавших правил, а не перебором вариантов —
   класс стоял на месте, а заливка оставалась мятной. */
.ui-screen .ui-button.ui-button--danger {
  background: none;
  border: 1px solid var(--danger);
  color: var(--danger);
}

.access__consent {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin: 0 0 8px;
  color: var(--text-muted);
  font: var(--text-caption) / var(--text-caption-lh) var(--font-sans);
  cursor: pointer;
}

.access__consent input { flex: none; margin: 2px 0 0; }

/* Причина, по которой поле не принимает ввод. Стоит ПОД полем, а не
   подсказкой внутри него: подсказка исчезает при вводе, а это поле ввода
   не принимает вовсе, и причина должна быть видна всегда. */
.ui-field__note {
  margin: 4px 0 0;
  color: var(--text-muted);
  font: var(--text-caption) / var(--text-caption-lh) var(--font-sans);
}

.ui-field__input:disabled { opacity: .6; cursor: not-allowed; }

/* Запись: кто и когда над текстом. В ленте это есть, а на экране записи
   терялось — человек, перешедший из ленты, лишался и автора, и времени. */
.post__top {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 12px;
  margin: 0 0 12px;
  color: var(--text-muted);
  font: var(--text-caption) / var(--text-caption-lh) var(--font-sans);
}

.post__text { margin: 0; color: var(--text); }

/* Выдержка в находке поиска: приглушённая, в две строки. Она отвечает на
   «почему нашлось», и заслонять ею заголовок нельзя. */
.found__snippet {
  margin: 2px 0 0;
  color: var(--text-muted);
  font: var(--text-caption) / var(--text-caption-lh) var(--font-sans);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Подписка в чужом профиле: два действия рядом и строка ответа под ними.
   Кнопки две потому, что состояния подписки служба не отдаёт, — подробности
   в самом экране. */
.profile__follow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin: 16px 0 0;
}

.profile__follow p {
  flex-basis: 100%;
  margin: 0;
  color: var(--text-muted);
}

/* Выдача ключа второго фактора: ключ и ссылка «открыть в приложении» стояли
   встык — «OBRAZEC…Открыть в приложении» читалось одним словом. Ключ здесь
   набирают руками, и слипшийся хвост человек перенесёт в приложение вместе
   с ним. */
[data-step="enrolling"] { display: grid; gap: 8px; justify-items: start; }

[data-secret] {
  font-family: var(--font-mono);
  overflow-wrap: anywhere;
}
