/* Nexus web — application styles. Layout: sidebar + reading column; the live turn renders
   the agent's actual §5 stages (latency as visible intelligence — the NX One thesis). */

* { box-sizing: border-box; margin: 0; padding: 0; }

[hidden] { display: none !important; }   /* view switching must beat explicit display values */

html, body { height: 100%; }

/* КЕГЛЬ ЗАДАЁМ МЫ, А НЕ БРАУЗЕР (31.08, третья жалоба владельца за месяц: «при стриме текст
   меньшего размера, после завершения становится крупнее»).
   
   ПОЧЕМУ ЭТО НЕ НАХОДИЛОСЬ ПО СТИЛЯМ. Разбор 26.08 честно заключил: «кегль текста при этом не
   менялся вовсе». В стилях — не менялся. Его меняет САМ БРАУЗЕР: мобильные Safari и Chrome
   раздувают текст узких блоков («font boosting»), и коэффициент раздутия ПЕРЕСЧИТЫВАЕТСЯ, когда
   блок меняется. В конце хода из карточки исчезают строки работы (`stages.remove()`) — раскладка
   меняется, и тот же самый текст перерисовывается крупнее. На компьютере этого нет вовсе, поэтому
   месяц правок стрима ничего не давал: чинили не ту сторону.
   
   `100%` — это НЕ запрет масштабирования пальцами (его отключает `user-scalable=no` в viewport, и
   его у нас нет и не будет). Это отказ от АВТОМАТИЧЕСКОГО раздутия: текст ровно того кегля,
   который написан в стилях, в любом состоянии карточки. */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }

body {
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  line-height: var(--lh);
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

#app { height: 100%; }

button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

/* ── brand ─────────────────────────────────────────────────────────────────── */

.brand-mark {
  display: inline-block;
  width: 64px; height: 64px;
  color: var(--accent);
  user-select: none;
}
.brand-mark svg { width: 100%; height: 100%; display: block; }
.brand-mark.small { width: 28px; height: 28px; }

/* ── login ─────────────────────────────────────────────────────────────────── */

/* ОДИН ЭКРАН ВХОДА НА ВСЕ РАЗМЕРЫ (18.08, слова владельца: «кто-то откроет с мака, кто-то с
   айпада, кто-то с телефона — и везде будет разный экран»).
   Так и было: шире 860px страница раскладывалась в ДВЕ колонки с левым выравниванием, а объяснение
   продукта и правовые ссылки внутри них оставались по центру — половина экрана прижата влево,
   половина по центру. Уже 860px это выглядело чужой вёрсткой, а не одним продуктом.
   Теперь колонка ОДНА, по центру, с потолком ширины — ровно то, что делают на экранах входа
   ChatGPT, Claude и Apple ID: телефон, планшет и мак показывают одно и то же, отличаясь только
   полями. Мелочь, из-за которой раньше и возникало «немного неаккуратно». */
.login {
  min-height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: var(--space-6);
  max-width: 30rem;
  margin: 0 auto;
  padding: clamp(var(--space-5), 6vh, var(--space-6)) var(--space-5);
  text-align: center;
}

.login-brand { display: flex; flex-direction: column; align-items: center; gap: var(--space-4); }
.brand-name { font-size: var(--fs-hero); font-weight: 750; letter-spacing: -0.02em; }
.brand-tagline { font-size: var(--fs-l); color: var(--muted); }
/* Объяснение продукта и правовые ссылки на экране входа: человек должен понять, куда он пускает
   свою переписку, ДО того, как нажмёт «Продолжить». */
.login-what {
  font-size: var(--fs-s); color: var(--muted); line-height: 1.5;
  max-width: 34rem; margin: var(--space-3) auto 0; text-align: center;
}
.login-legal { font-size: var(--fs-xs); color: var(--muted); text-align: center; margin-top: var(--space-4); }
.login-legal a { color: var(--muted); text-decoration: underline; }

.login-card {
  width: 100%;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-l);
  box-shadow: var(--shadow-2);
  padding: var(--space-6) var(--space-5);
  display: flex; flex-direction: column; align-items: center; gap: var(--space-3);
  text-align: center;
  width: 100%; max-width: 23rem; justify-self: center;
}
.login-title { font-size: var(--fs-xl); font-weight: 650; letter-spacing: -0.01em; }
.login-widget { min-height: 44px; display: grid; place-items: center; }
.login-hint { font-size: var(--fs-s); color: var(--muted); max-width: 24rem; }
.login-status { font-size: var(--fs-s); color: var(--danger); min-height: 1.2em; }

/* ШИРОКИЙ ЭКРАН — ДВЕ КОЛОНКИ (19.08, слова владельца: «когда большой экран, по бокам огромное
   пространство, выглядит странно»).
   Двухколоночный вид у нас уже был и был снят 17.08 — но снят из-за МЕЛОЧЕЙ, а не из-за самой
   идеи: объяснение продукта и правовые ссылки внутри левой колонки оставались по центру, и
   получалось «половина экрана прижата влево, половина по центру». Здесь те три мелочи и
   исправлены — выравнивание бренда, отмена auto-полей у объяснения и явное место правовых
   ссылок в первой колонке.
   ТЕЛЕФОН НЕ МЕНЯЕТСЯ НИ НА БАЙТ: всё правило живёт внутри `min-width`, а базовые стили выше —
   это и есть телефонный вид. Порог 861px — тот же, что у оболочки ниже; второго брейкпоинта в
   продукте не заводится. */
@media (min-width: 861px) {
  .login {
    display: grid;
    grid-template-columns: 1.15fr 1fr;   /* бренду места больше: он объясняет, карточка действует */
    align-items: center;
    max-width: 68rem;
    text-align: left;
    padding: var(--space-6) var(--space-5);
  }
  .login-brand { align-items: flex-start; }
  .login-what { margin-inline: 0; text-align: left; }
  /* Третий ребёнок сетки без явного места уезжал во вторую строку первой колонки — ровно та
     неаккуратность, из-за которой двухколоночный вид и сняли. */
  .login-legal { grid-column: 1; text-align: left; }
}

/* ── shell layout ──────────────────────────────────────────────────────────── */

.shell {
  height: 100%;
  display: grid;
  grid-template-columns: var(--sidebar-w) 1fr;
  transition: grid-template-columns var(--dur) var(--ease);
}

.sidebar {
  background: var(--surface);
  border-right: 1px solid var(--border);
  display: flex; flex-direction: column;
  min-height: 0;
  transition: transform var(--dur) var(--ease);
}

.sidebar-head { padding: var(--space-4); display: flex; flex-direction: column; gap: var(--space-4); }
.brand-mini { display: flex; align-items: center; gap: var(--space-2); font-weight: 650; }

/* the collapse control sits at the far end of the brand row (ChatGPT/Claude); it folds the
   sidebar away on desktop and the floating ☰ brings it back. State persists in localStorage. */
.sidebar-collapse {
  margin-left: auto;
  width: 30px; height: 30px; border-radius: var(--radius-s);
  display: grid; place-items: center; flex-shrink: 0;
  color: var(--muted); font-size: 1.15rem; line-height: 1;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.sidebar-collapse:hover { background: var(--hover); color: var(--text); }

.new-chat {
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-m);
  background: var(--ink);
  color: var(--ink-on);
  font-weight: 600;
  transition: filter var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.new-chat:hover { filter: brightness(1.08); }
.new-chat:active { transform: scale(0.985); }

/* «События» — второй строкой панели, сразу под «новым разговором»: то же место и тот же порядок,
   что в меню приложения. Вид спокойнее, чем у «нового»: это переход, а не действие. */
.sidebar-events {
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-m);
  background: var(--raised);
  color: var(--text);
  font-size: var(--fs-s);
  text-align: left;
  transition: background var(--dur) var(--ease);
}
.sidebar-events:hover { background: var(--hover); }

.conv-list { flex: 1; overflow-y: auto; padding: 0 var(--space-2); min-height: 0; }

/* ПОИСК ПО СПИСКУ РАЗГОВОРОВ (§249): стоит НАД прокруткой, поэтому не уезжает вместе со строками
   и всегда под рукой — тот же уклад, что у панели над списком чатов в настройках. */
.conv-search {
  margin: 0 var(--space-3) var(--space-2);
  width: calc(100% - var(--space-3) * 2);
  font-size: var(--fs-s);
}
.conv-empty { color: var(--muted); font-size: var(--fs-s); padding: var(--space-3); }
/* Заголовок группы («Закреплённые» / «Все разговоры») — подпись, а не строка списка. */
.conv-group {
  color: var(--muted); font-size: var(--fs-xs); text-transform: uppercase;
  letter-spacing: 0.04em; padding: var(--space-3) var(--space-3) var(--space-1);
}

.conv-item {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-m);
  cursor: pointer;
  color: var(--muted);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.conv-item:hover { background: var(--hover); color: var(--text); }
.conv-item.active { background: var(--accent-soft); color: var(--text); }
.conv-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.conv-title {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--fs-s);
}
/* Начало последней реплики — вторичным цветом и в одну строку, как в приложении. */
.conv-brief {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--fs-xs, .78rem); color: var(--muted);
}

/* ЗНАК ЗАКРЕПЛЁННОГО (19.08, просьба владельца: «никак не помечен, нужен аккуратный знак»).
   Группа «Закреплённые» была, но внутри строки — ничего: перетащив взгляд на список, человек
   не видел, какой из разговоров он держит под рукой. Значок вторичным цветом и мельче текста:
   это пометка, а не второе название. */
.conv-pin {
  margin-left: var(--space-1); font-size: .8em; color: var(--muted);
  flex: 0 0 auto; line-height: 1;
}
/* МЕСТО ПОД КНОПКУ ЗАНЯТО ВСЕГДА (09.09, пункт 3 владельца: «наведение дёргает весь список»).
   Здесь стоял display:none → на наведении кнопка ПОЯВЛЯЛАСЬ, строка перевёрстывалась и заголовки
   прыгали под курсором. visibility прячет, не отдавая места, — список неподвижен. */
.conv-actions { visibility: hidden; display: inline-flex; gap: 2px; flex-shrink: 0; }

/* МЕНЮ РАЗГОВОРА (14.08). Раньше в строке стояли три значка; на телефоне они показывались всегда
   и занимали 132 пикселя, отжимая название. Теперь одна кнопка «⋯», а действия — в меню.
   Оно висит над списком, поэтому строка не растёт и список не прыгает.

   С 15.08 меню — СЛОЙ НАД ОКНОМ: оно лежало внутри прокручиваемого списка, и тот обрезал его
   краями. У первых разговоров меню уходило под шапку панели — «список прячется за кнопкой
   События» (скриншот владельца). Положение считает JS от самой кнопки; здесь z-index выше
   боковой панели и затемнения. */
.conv-menu {
  position: fixed; z-index: 60; min-width: 180px;
  display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius-m); padding: var(--space-1);
  box-shadow: 0 10px 30px rgba(0, 0, 0, .35);
}
.conv-menu-item {
  background: none; border: 0; text-align: left; cursor: pointer;
  color: var(--text); font: inherit; font-size: var(--fs-s);
  padding: var(--space-2); border-radius: var(--radius-s); min-height: 44px;
}
.conv-menu-item:hover, .conv-menu-item:focus-visible { background: var(--bg); }
/* Позиционируем от строки, а не от окна: меню обязано стоять у своего разговора. */
.conv-item { position: relative; }
/* Поле переименования занимает всю строку: имя длиннее ширины панели — обычное дело. */
.conv-rename { width: 100%; font-size: var(--fs-s); }
.conv-item:hover .conv-actions, .conv-item:focus-within .conv-actions { visibility: visible; }
.icon-btn {
  width: 24px; height: 24px; border-radius: 6px;
  display: grid; place-items: center;
  color: var(--muted); font-size: var(--fs-xs);
}
.icon-btn:hover { background: var(--raised); color: var(--text); }
.icon-btn.danger:hover { background: var(--danger-soft); color: var(--danger); }

.account-block {
  border-top: 1px solid var(--border);
  padding: var(--space-3) var(--space-4) var(--space-2);
  display: flex; flex-direction: column; gap: var(--space-2);
}
.account-row { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-2); }
.account-plan { font-size: var(--fs-xs); color: var(--muted); text-transform: uppercase; letter-spacing: 0.08em; }
.quota-text { font-size: var(--fs-xs); color: var(--muted); font-variant-numeric: tabular-nums; }
.quota-bar {
  height: 3px; border-radius: 2px; background: var(--border);
  overflow: hidden;
}
.quota-fill {
  display: block; height: 100%; width: 0;
  background: var(--accent);
  transition: width var(--dur) var(--ease);
}
.account-block.exhausted .quota-text { color: var(--danger); }

.sidebar-foot {
  border-top: 1px solid var(--border);
  padding: var(--space-3) var(--space-4);
  display: flex; align-items: center; justify-content: flex-end; gap: var(--space-2);
}
/* ТИХОЕ ДЕЙСТВИЕ — ОДИН ВИД НА ВСЕ КНОПКИ ЭТОГО РОДА (10.09, слово владельца: «„Исходник" не
   воспринимается как кнопка… нужна обводка именно как кнопка, но прям очень мягкая и приятная на
   глаз, причём для всех кнопок похожего назначения — одно и то же оформление»).

   ЧТО БЫЛО. У вторичных действий не было обводки ВООБЩЕ — только цвет и отступ. «Исходник»,
   «Открыть сообщение», «+ Событие», «К разговорам» читались как текст: человек не понимал, что по
   ним можно нажать.

   ЧТО СТАЛО. Один класс на весь дом: волосяная рамка цвета `--border` (та же линия, которой
   разделены строки, — поэтому она ни с чем не спорит), радиус кнопки, прозрачный фон. Наведение
   не красит рамку акцентом: акцент у нас значит «живое» и «опасное», а это спокойное действие —
   рамка лишь набирает контраст.

   ПОЧЕМУ ОДИН КЛАСС, А НЕ ПЯТЬ ПОХОЖИХ: одинаковые по назначению кнопки, оформленные порознь,
   расходятся при первой же правке, и человек перестаёт узнавать их с одного взгляда. */
.ghost,
.event-link,
.event-quote > summary,
.ev-around,
.said-open {
  font-size: var(--fs-s); color: var(--muted);
  padding: var(--space-1) var(--space-2); border-radius: var(--radius-s);
  border: 1px solid var(--border);
  background: transparent;
  display: inline-flex; align-items: center; gap: var(--space-1);
  cursor: pointer; text-decoration: none;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
              color var(--dur) var(--ease);
}
.ghost:hover,
.event-link:hover,
.event-quote > summary:hover,
.ev-around:hover,
.said-open:hover {
  background: var(--hover); color: var(--text); border-color: var(--border-strong);
}
/* Тихое действие остаётся тихим и в фокусе с клавиатуры: обводка усиливается, а не подменяется. */
.ghost:focus-visible,
.event-link:focus-visible,
.event-quote > summary:focus-visible,
.ev-around:focus-visible,
.said-open:focus-visible { border-color: var(--border-strong); color: var(--text); }
.ghost:disabled,
.ev-around:disabled { color: var(--muted); cursor: default; border-color: var(--border); }
/* Раскрывашка «Исходник» — кнопка, а не строка списка: системный маркер тут лишний. */
.event-quote > summary { list-style: none; }
.event-quote > summary::-webkit-details-marker { display: none; }

/* ── main pane ─────────────────────────────────────────────────────────────── */

.main { display: flex; flex-direction: column; min-width: 0; min-height: 0; }

.thread {
  flex: 1; overflow-y: auto;
  /* Снизу — высота панели ввода (её меряет shell.js) плюс воздух: под панелью ДОЛЖНО хватать
     места, иначе последняя строка ответа останется под ней, и долистать до неё будет нельзя.
     Запасное значение — на случай, если измерение ещё не пришло (первый кадр). */
  padding: var(--space-5) var(--space-4) calc(var(--dock-h, 5.5rem) + var(--space-3));
  scroll-behavior: smooth;
}
.thread > * { max-width: var(--column-w); margin-left: auto; margin-right: auto; }

/* ПУСТОЙ ЭКРАН ЦЕНТРИРУЕТСЯ ПО ВИДИМОМУ, А НЕ ПО КОРОБКЕ. Ленте снизу добавлен отступ в высоту
   панели ввода — для СПИСКА это правильно, а на пустом экране «Чем помочь?» уехало бы вверх на
   половину этой высоты: центр коробки и центр видимой части перестали совпадать. Пусто — списка
   нет, и отступ не нужен. */
.thread:has(> .empty-state) { padding-bottom: var(--space-3); }

/* О ЧЁМ СПРОСИТЬ (19.08). Не «примеры», а готовые вопросы по его же памяти: нажатие ОТПРАВЛЯЕТ.
   Пилюлями, а не списком: так их читают как предложения, а не как инструкцию. */
.empty-asks { display: flex; flex-direction: column; gap: var(--space-2);
  margin-top: var(--space-5); width: 100%; max-width: 30rem; }
.ask-chip {
  text-align: left; padding: var(--space-3) var(--space-4);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-l);
  color: var(--text); font: inherit; font-size: var(--fs-s); cursor: pointer;
}
.ask-chip:hover { background: var(--hover); border-color: var(--border-strong); }

.empty-state {
  height: 100%; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: var(--space-2);
  color: var(--muted); text-align: center;
}
.empty-state h2 { color: var(--text); font-size: var(--fs-xl); letter-spacing: -0.01em; }

.load-earlier {
  display: block; margin: 0 auto var(--space-4);
  font-size: var(--fs-s); color: var(--muted);
  padding: var(--space-1) var(--space-3); border-radius: 999px;
  border: 1px solid var(--border);
}
.load-earlier:hover { background: var(--hover); color: var(--text); }

/* messages */
.msg { margin-bottom: var(--space-5); }

.msg-user { display: flex; flex-direction: column; align-items: flex-end; }
.msg-user .msg-body {
  background: var(--hover);
  border: 1px solid var(--border);
  border-radius: var(--radius-l) var(--radius-l) var(--radius-s) var(--radius-l);
  padding: var(--space-3) var(--space-4);
  max-width: 85%;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.msg-assistant .msg-body { overflow-wrap: anywhere; }
.msg-assistant .md p { margin-bottom: var(--space-3); }
.msg-assistant .md p:last-child { margin-bottom: 0; }
.msg-assistant .md ul, .msg-assistant .md ol { margin: 0 0 var(--space-3) 1.4em; }
.msg-assistant .md li { margin-bottom: var(--space-1); }
.msg-assistant .md code {
  font-family: var(--font-mono); font-size: 0.88em;
  background: var(--raised); border: 1px solid var(--border);
  border-radius: 5px; padding: 0.1em 0.35em;
}
.msg-assistant .md pre {
  background: var(--raised); border: 1px solid var(--border);
  border-radius: var(--radius-m); padding: var(--space-3) var(--space-4);
  overflow-x: auto; margin-bottom: var(--space-3);
}
.msg-assistant .md pre code { background: none; border: none; padding: 0; font-size: 0.86em; }

.msg-meta {
  display: flex; align-items: center; gap: var(--space-2);
  margin-top: var(--space-2);
  font-size: var(--fs-xs); color: var(--muted);
  font-variant-numeric: tabular-nums;
  opacity: 0; transition: opacity var(--dur) var(--ease);
}
.msg-user .msg-meta { justify-content: flex-end; }
.msg:hover .msg-meta, .msg:focus-within .msg-meta { opacity: 1; }
/* НА ТЕЛЕФОНЕ НАВЕДЕНИЯ НЕ БЫВАЕТ (08.08). Время ответа и кнопка «копировать» проявлялись только
   при наведении мыши — то есть с телефона их не существовало вовсе: ни скопировать ответ, ни
   узнать, когда он пришёл. В приложении копирование видно всегда, и это правильная сторона.
   Спрашиваем не про ширину экрана, а про сам способ ввода: планшет с мышью тоже бывает. */
@media (hover: none) {
  .msg-meta { opacity: 1; }
  /* ДЕЙСТВИЯ НАД РАЗГОВОРОМ — ТОЖЕ (09.08). Закрепить, переименовать, удалить показывались только
     по наведению мыши: с телефона их не существовало вовсе, и человек не мог ни удалить разговор,
     ни закрепить его. Тот же довод, что строкой выше, — просто его забыли применить сюда. */
  .conv-actions { visibility: visible; }
  /* ПАЛЕЦ ТОЛЩЕ КУРСОРА. Apple требует 44pt, Google — 48dp; здесь стояло 24 px, и по трём
     значкам подряд человек промахивался. Поднимаем только там, где нет мыши: на компьютере
     мелкие значки уместны и не мешают. */
  .icon-btn { width: 44px; height: 44px; }
  .chip-drop { width: 44px; height: 44px; }
  .msg-copy { min-height: 44px; display: inline-flex; align-items: center; }
  .sidebar-foot .ghost { min-height: 44px; }
}

.msg-copy {
  padding: 2px var(--space-2); border-radius: var(--radius-s);
  font-size: var(--fs-xs); color: var(--muted); line-height: 1.6;
}
.msg-copy:hover { background: var(--hover); color: var(--text); }
.msg-copy.copied { color: var(--accent); }

.msg-failed .msg-error {
  margin-top: var(--space-2);
  color: var(--danger); background: var(--danger-soft);
  border-radius: var(--radius-m); padding: var(--space-2) var(--space-3);
  font-size: var(--fs-s);
}

/* the live turn: stage timeline + draft */
.stages { display: flex; flex-direction: column; gap: var(--space-1); margin-bottom: var(--space-3); }
/* once the answer starts arriving the reader is READING — the work log collapses to one quiet
   line instead of competing with the text it produced */
.stages.compact { gap: 0; margin-bottom: var(--space-2); }
.stages.compact .stage-line:not(:last-child) { display: none; }
.stages.compact .stage-line { color: var(--muted); font-size: var(--fs-xs); }
.stages.compact .stage-dot { background: var(--muted); animation: none; }
.stage-line {
  display: flex; align-items: center; gap: var(--space-2);
  font-size: var(--fs-s); color: var(--muted);
}
.stage-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--border-strong);
  flex-shrink: 0;
}
.stage-line.active { color: var(--text); }
.stage-line.active .stage-dot {
  background: var(--accent);
  animation: pulse 1.2s var(--ease) infinite;
}
.stage-count { color: var(--muted); font-variant-numeric: tabular-nums; margin-left: var(--space-1); }
@keyframes pulse {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.45); opacity: 0.55; }
}

/* A thin, quiet caret — a text cursor, not an alarm. Red is reserved for identity and
   primary actions; a streaming answer must never look like an error state.
   It hangs off the LAST rendered block, not the container: markdown output is block-level, so a
   container ::after drops to its own line and squats where the sources panel later appears.
   Gated by `.streaming`: the caret means «typing right now». After the synthesis deltas stop,
   verification runs for seconds with the answer already fully drawn — a caret blinking there reads
   as «stuck» (it did, on screen). The honest «still working» signal in that window is the labeled
   «Сверяю…» stage line, not a cursor on finished text. */
/* КАРЕТКА ВСТАЁТ В ТЕКСТ, А НЕ ПОД НИМ (16.08, «у меня опять появилась каретка»). Прежний
   селектор `> :last-child::after` верен только для абзаца: у списка ::after становится последним
   ребёнком САМОГО СПИСКА — блочные <li> выталкивают его на собственную пустую строку под последним
   пунктом, и человек видит мигающую палку, оторванную от текста. Ответы продукта сплошь кончаются
   списками, поэтому дефект был почти в каждом ответе. Теперь каретка адресуется в последний пункт
   списка и в код — туда, где реально дописывается текст. */
.draft.streaming > p:last-child::after,
.draft.streaming > ul:last-child > li:last-child::after,
.draft.streaming > ol:last-child > li:last-child::after,
.draft.streaming > pre:last-child > code::after {
  content: '';
  display: inline-block;
  width: 2px; height: 1.05em;
  vertical-align: -0.16em;
  margin-left: 2px;
  background: var(--muted);
  animation: caretPulse 1.15s ease-in-out infinite;
}
/* Плавный пульс вместо жёсткого steps-мигания: премиальнее и не дёргает глаз (02.09). */
@keyframes caretPulse { 0%, 100% { opacity: 0.9; } 50% { opacity: 0.12; } }
/* ГОТОВЫЙ АБЗАЦ ПРОЯВЛЯЕТСЯ ИЗ ПУСТОТЫ (02.09, «как у топов»). Осевший кусок дописывается с
   этим классом; после проявления класс снимается, чтобы не мешать выделению и не висеть. */
@keyframes materialize { from { opacity: 0; } to { opacity: 1; } }
.md-settled .materialize { animation: materialize 0.42s ease-out both; }
@media (prefers-reduced-motion: reduce) { .md-settled .materialize { animation: none; } }

/* ИСПРАВЛЕННЫЙ КУСОК ПРОЯВЛЯЕТСЯ, А НЕ ПОДМЕНЯЕТСЯ (17.08).
   Здесь стояло приглушение всего ответа на время сверки — честное, пока сверка возвращала весь
   текст заново. Теперь она называет точные замены и в обычном случае не меняет ничего, поэтому
   гасить готовый текст стало нечестно: рывка, к которому готовили, больше нет. Когда правка всё
   же есть, текст не подменяется мгновенно, а проявляется — глазу это читается как «дописали», а
   не как «подменили». */
/* ТИХАЯ СТРОКА «СВЕРЯЮ» ПОД ГОТОВЫМ ОТВЕТОМ (01.09). Видна всегда, пока сверка идёт, — в
   отличие от `msg-meta`, который проявляется по наведению: это не украшение, а признак работы,
   и прятать его значило бы вернуть то самое молчание. */
/* Готовая часть и хвост живого ответа — два узла, но для вёрстки это ОДИН поток текста:
   иначе между ними появился бы лишний блок и отступ (01.09). */
.md-settled, .md-tail { display: contents; }

.msg-meta.checking { opacity: 1; display: flex; align-items: center; gap: var(--space-2); }

.msg-body.settling {
  animation: settle .26s ease;
}
@keyframes settle {
  from { opacity: .55; }
  to   { opacity: 1; }
}

/* «Ждём телефон» — состояние, а не примечание: чуть заметнее соседних подсказок, но не тревога
   (красное здесь врало бы — ничего не сломано). До 16.08 класс существовал без единого правила. */
.depth-waiting {
  color: var(--text);
  background: var(--accent-soft);
  border-radius: 8px;
  padding: 6px 10px;
}

/* ЗАЯВКА «НАПИШИ ЗА МЕНЯ» — карточка под ответом. Не тревога и не ошибка: продукт ничего не
   натворил, он ПРЕДЛАГАЕТ. Поэтому спокойная рамка, а не красная; «Отправить» — главное действие
   (акцент), «Не отправлять» — тихое рядом. */
.outbox {
  margin-top: var(--space-3);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: var(--space-3);
  background: var(--surface);
}
.outbox-draft + .outbox-draft { margin-top: var(--space-3); }
.outbox-to {
  font-size: var(--fs-s);
  color: var(--muted);
  margin-bottom: 4px;
}
.outbox-subject { font-weight: 650; margin-bottom: var(--space-1); }
.outbox-text { white-space: pre-wrap; }
.outbox-act {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

/* evidence */
.evidence {
  margin-top: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-m);
  background: var(--surface);
}
.evidence summary {
  cursor: pointer; padding: var(--space-2) var(--space-3);
  font-size: var(--fs-s); color: var(--muted); user-select: none;
}
.evidence[open] summary { border-bottom: 1px solid var(--border); color: var(--text); }
.ev-list { list-style: none; padding: var(--space-2) var(--space-3); display: flex; flex-direction: column; gap: var(--space-3); }
.ev-item { display: flex; flex-direction: column; gap: 2px; }
.ev-title { font-weight: 600; font-size: var(--fs-s); }
.ev-meta { font-size: var(--fs-xs); color: var(--muted); }
.ev-snippet { font-size: var(--fs-s); color: var(--muted); overflow-wrap: anywhere; }

/* МИНИ-ДИАЛОГ ВОКРУГ УЛИКИ (§245): разговор, а не обрезок. Читается как переписка — имя над
   репликой, свои справа-приглушённо, сама улика выделена полосой. Красного здесь нет: это
   запись, а не тревога. */
.ev-around-wrap { margin-top: .4rem; }
.ev-around { margin-top: .1rem; }
.ev-dialog {
  margin-top: .5rem; display: flex; flex-direction: column; gap: .4rem;
  padding: .55rem .65rem; border-radius: 10px;
  background: var(--surface-2, rgba(127, 127, 127, .07));
}
.ev-turn { display: flex; flex-direction: column; gap: 2px; }
.ev-turn.anchor { border-left: 2px solid var(--accent, #c62828); padding-left: .5rem; }
.ev-who { font-size: .72rem; color: var(--muted, #888); }
.ev-said { font-size: .86rem; white-space: pre-wrap; }
.ev-turn.mine .ev-said { color: var(--muted, #888); }

/* composer */
/* ПЛАШКА — ОТДЕЛЬНЫЙ ПРЕДМЕТ НАД ЛЕНТОЙ, а не край экрана. Скруглена по высоте, приподнята
   тенью, слегка размывает то, что под ней. Растёт вниз-вверх вместе с полем: у поля потолок в
   пять строк, и на этом углы перестают быть полукруглыми — так же у всех, на кого равняемся. */
.composer {
  display: flex; align-items: flex-end; gap: var(--space-2);
  max-width: var(--column-w);
  width: 100%;
  margin: 0 auto;
  padding: var(--space-2);
  background: color-mix(in srgb, var(--raised) 88%, transparent);
  backdrop-filter: blur(16px) saturate(1.5);
  -webkit-backdrop-filter: blur(16px) saturate(1.5);
  border: 1px solid var(--border);
  border-radius: 26px;
  box-shadow: 0 10px 30px -12px rgba(0, 0, 0, .35), 0 2px 8px -4px rgba(0, 0, 0, .2);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
/* Без размытия полупрозрачность превращается в грязь: там плашка просто плотная. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .composer { background: var(--raised); }
}
/* Обводка загорается у ПЛАШКИ, а не у поля внутри неё: рамка в рамке — это две коробки. */
.composer:focus-within {
  border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
  box-shadow: 0 10px 30px -12px rgba(0, 0, 0, .35), 0 0 0 3px var(--accent-soft);
}
/* Поле внутри плашки — БЕЗ СВОЕЙ КОРОБКИ: своя рамка и свой фон внутри чужой рамки читаются
   как две панели одна в другой. Коробка теперь одна, и она вся — плашка. */
.composer-input {
  flex: 1; resize: none;
  font: inherit; color: inherit;
  background: none;
  border: none;
  padding: 9px var(--space-2);
  max-height: 200px;
  overflow-y: hidden;              /* autoGrow flips to auto only past max-height */
  scrollbar-width: thin;
}
.composer-input:focus { outline: none; }
.composer-input::placeholder { color: var(--muted); }

.composer-send {
  width: 42px; height: 42px; flex-shrink: 0;
  border-radius: 50%;
  /* red — the primary action carries the brand — but with a little dimension: a soft top highlight
     over the accent, a low red glow, and a hairline inner sheen. Refined, not flat, not busy. */
  background: radial-gradient(130% 130% at 50% 0%,
              color-mix(in srgb, var(--accent) 82%, #fff) 0%, var(--accent) 58%);
  color: var(--accent-ink);
  display: grid; place-items: center;
  font-size: 1.05rem; font-weight: 700;
  box-shadow: 0 2px 9px -2px color-mix(in srgb, var(--accent) 50%, transparent),
              inset 0 1px 0 color-mix(in srgb, #fff 22%, transparent);
  transition: filter var(--dur) var(--ease), transform var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease), opacity var(--dur) var(--ease);
}
.composer-send:hover {
  filter: brightness(1.05);
  box-shadow: 0 4px 14px -2px color-mix(in srgb, var(--accent) 62%, transparent),
              inset 0 1px 0 color-mix(in srgb, #fff 28%, transparent);
}
.composer-send:active { transform: scale(0.93); }
.composer-send:disabled {
  background: none;
  color: var(--muted);
  box-shadow: none;
  cursor: default;
}

/* прикреплённое (§15) — скрепка слева от поля, приложенное над ним */
.file-picker { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }

.composer-attach {
  width: 42px; height: 42px; flex-shrink: 0;
  border-radius: 50%;
  background: transparent; color: var(--muted);
  border: 1px solid var(--border-strong);
  display: grid; place-items: center;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease),
              background var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.composer-attach:hover { color: var(--text); border-color: var(--accent); background: var(--surface); }
.composer-attach:active { transform: scale(0.93); }
.attach-glyph { display: block; width: 20px; height: 20px; }
.attach-glyph svg { width: 100%; height: 100%; display: block; }

.attachments {
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  max-width: var(--column-w); width: 100%; margin: 0 auto var(--space-2);
  padding: 0 var(--space-2);
}
.attach-chip {
  display: inline-flex; align-items: center; gap: 6px;
  max-width: 100%;
  padding: 4px 4px 4px 10px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-l);
  background: var(--surface);
  font-size: var(--fs-s);
}
.attach-chip .chip-mark { width: 14px; height: 14px; color: var(--muted); flex-shrink: 0; }
.attach-chip .chip-mark svg { width: 100%; height: 100%; display: block; }
.attach-chip .chip-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 22ch; }
.attach-chip.pending { opacity: 0.65; }
.attach-chip.unread { border-color: var(--accent); color: var(--muted); }
.chip-drop {
  width: 22px; height: 22px; flex-shrink: 0;
  border-radius: 50%; background: transparent; color: var(--muted);
  display: grid; place-items: center; font-size: 1rem; line-height: 1;
}
.chip-drop:hover { background: var(--border); color: var(--text); }

/* приложенное в самой ленте — имя и вид, без содержимого */
.msg-files { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: var(--space-2); }
.msg-file {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 8px; border-radius: var(--radius-l);
  background: var(--surface); border: 1px solid var(--border);
  font-size: var(--fs-xs); color: var(--muted);
  overflow-wrap: anywhere;
}
.msg-file.unread { border-style: dashed; }

/* ── sources (settings) ────────────────────────────────────────────────────── */

/* ПОЛЕ ВВОДА ВИСИТ НАД ЛЕНТОЙ (17.08, слова владельца: «поисковая строка загораживает ответ,
   сделай как в тг, чате гпт, клоде — поисковая строка, клава, кнопка отправки, кнопка файлов
   все они висят над чатом»).
   БЫЛО: панель стояла ОТДЕЛЬНОЙ СТРОКОЙ под лентой, того же цвета и без единой границы. Лента
   кончалась ровно об её край — последняя строка ответа упиралась в поле вплотную, а при прокрутке
   текст обрезался о невидимый шов. Глазами это читается именно так, как он сказал: панель
   загораживает ответ.
   СТАЛО: панель — СЛОЙ НАД лентой (полупрозрачная, с размытием), а лента получает снизу отступ
   ровно в её высоту. Текст уходит под панель и проступает сквозь неё, ничего не обрезаясь, а до
   последней строки всегда можно долистать. Высоту панели меряет ResizeObserver в shell.js: поле
   растёт до пяти строк, и отступ обязан расти с ним. */
.chat-view { display: flex; flex-direction: column; min-height: 0; flex: 1; position: relative; }

.composer-dock {
  display: flex; flex-direction: column;
  position: absolute; inset: auto 0 0 0; z-index: 5;
  /* САМА ПОЛОСА — ПУСТОЕ МЕСТО. Здесь стоял сплошной полупрозрачный брусок во всю ширину, и
     владелец сказал прямо: «поисковая строка буквально висит над чатом, мне нужно ровно то же
     самое, а не один сплошной блок». Так и сделано у ChatGPT, Telegram и Claude: над лентой
     висит ОТДЕЛЬНАЯ скруглённая плашка с полями по бокам, а лента видна и вокруг неё, и под ней.
     Фон и размытие переехали на саму плашку — ниже. */
  background: none;
  padding: 0 var(--space-3) calc(var(--space-3) + env(safe-area-inset-bottom));
  pointer-events: none;          /* мимо плашки палец попадает в ленту, а не в пустоту */
}
.composer-dock > * { pointer-events: auto; }
/* ЗАТУХАНИЯ НАД ПАНЕЛЬЮ БОЛЬШЕ НЕТ (17.08, вечер). Здесь стояла полоса в двадцать пикселей с
   переходом «фон → прозрачное», и владелец увидел ровно то, что она и есть: «какая-то полоса
   появилась вдоль поисковой строки». Беда известная: браузер уводит цвет к ПРОЗРАЧНО-ЧЁРНОМУ, и
   на тёмной теме середина перехода становится светлее фона, а на светлой — темнее. Получается
   видимый шов во всю ширину.
   Чинить его подбором цвета незачем: плашка и так размывает то, что под ней, а текст, доходящий
   до её края, — ровно то, как это выглядит у Telegram. Полоса убрана целиком. */

.settings-view {
  flex: 1; overflow-y: auto;
  padding: var(--space-5) var(--space-4);
}
.settings-view > * { max-width: 40rem; margin-left: auto; margin-right: auto; }

.settings-head {
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-2);
  margin-bottom: var(--space-5);
}
.settings-title { font-size: var(--fs-xl); letter-spacing: -0.01em; }

.settings-list { display: flex; flex-direction: column; gap: var(--space-4); }

.source-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-l);
  padding: var(--space-4) var(--space-5);
  box-shadow: var(--shadow-1);
}
.source-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-3); }
/* Официальный знак источника — те же файлы, что в приложении. Обтекание сеткой: знак слева,
   имя и подпись колонкой справа; размер как у строки настроек iOS. */
.source-head-lead { display: grid; grid-template-columns: auto 1fr; column-gap: var(--space-3);
  align-items: center; min-width: 0; }
.source-head-lead .source-desc { grid-column: 2; }
.source-mark { width: 28px; height: 28px; border-radius: 7px; grid-row: 1 / span 2;
  object-fit: contain; }
/* ОПТИЧЕСКИЙ РАЗМЕР, А НЕ ГЕОМЕТРИЧЕСКИЙ (11.09, слово владельца: «увеличь лого ватсапа, а герб
   чуть больше»). Одна коробка на все знаки честна только для знаков одной плотности. Плотный
   зелёный квадрат WhatsApp и ажурный герб с венком занимают её по-разному: у герба половина
   площади — просветы между листьями, и в общем размере он выглядит мельче соседей, хотя
   геометрически равен им. Поэтому равняем по ВОСПРИЯТИЮ. Специфичность двух классов (0-2-0)
   выше одного, так что порядок правил здесь ничего не решает. */
.source-mark.mark-whatsapp, .source-mark.mark-whatsapp_business { width: 32px; height: 32px; }
/* Герб не квадратный (133×155) и вписывается по высоте; скругление ему не нужно — углы прозрачны. */
.source-mark.mark-bmstu { width: 36px; height: 36px; border-radius: 0; }
.source-name { font-size: var(--fs-l); font-weight: 650; }
.source-desc { font-size: var(--fs-s); color: var(--muted); }
.source-scopes { font-size: var(--fs-s); color: var(--muted); margin-bottom: var(--space-3); }
.source-body { margin-top: var(--space-3); }

/* Хвост строки настройки: время и выключатель в одном ряду. Раньше поле времени стояло
   ОТДЕЛЬНОЙ строкой под галочкой — шесть строк на один вопрос «когда». */
.prefs-row-tail { display: flex; align-items: center; gap: var(--space-3); }
.prefs-row-tail .flow-input { width: auto; min-width: 0; }

/* «Это один и тот же человек?». Стороны стоят РЯДОМ и одинаково: вопрос симметричен, и
   выделить одну из них значило бы подсказать ответ. На узком экране они становятся друг под
   друга — сравнивать сжатые в ниточку колонки нельзя. */
.link-list { display: flex; flex-direction: column; gap: var(--space-4); margin-top: var(--space-3); }
.link-row { display: flex; flex-direction: column; gap: var(--space-2); }
/* КАЖДАЯ ПЕРСОНА — В СВОЁЙ РАМКЕ (30.08, «один или разные персоны — нужно оформить понятнее»).
   Две стороны стояли двумя колонками без границы между ними и читались как одна мешанина строк.
   Лёгкая подложка и рамка делают из них ДВЕ карточки, которые глаз сравнивает как пару. */
.link-side {
  font-size: var(--fs-s);
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-m);
  background: var(--surface);
}
.link-name { font-weight: 600; }
/* ПРИМЕТА ЧИТАЕТСЯ РАНЬШЕ ЧИСЕЛ. Именно по ней человек узнаёт, о ком речь, поэтому она темнее
   и плотнее строки «сколько сообщений и когда»: та отвечает на другой вопрос и ждать может.
   Длинное название группы переносится, а не режется многоточием: обрезанное имя разговора и есть
   потерянная примета. */
.link-mark { color: var(--text); overflow-wrap: anywhere; }
/* ПРИМЕТА-ССЫЛКА ВЫГЛЯДИТ ССЫЛКОЙ (08.09). Она ведёт в мессенджер и открывает человека одним
   касанием — если она неотличима от обычного текста, по ней не нажмут, и правка не состоится.
   Цвет ссылки продукта, а не подчёркивание: подчёркнутая строка спорит с именем над ней. */
a.link-mark { color: var(--accent); text-decoration: none; }
a.link-mark:hover,
a.link-mark:focus-visible { text-decoration: underline; }
/* ОТКУДА ЗНАЮ ЭТОГО ЧЕЛОВЕКА — тише приметы и темнее чисел: это ответ на «откуда», а не на «кто».
   Название группы переносится целиком: обрезанное — потерянная примета (см. соседа выше). */
.link-where-seen { color: var(--text); overflow-wrap: anywhere; }
.link-where { color: var(--muted); }
.link-answer { display: flex; gap: var(--space-2); margin-top: var(--space-1); }
.link-gone { font-size: var(--fs-s); color: var(--muted); }
@media (min-width: 560px) {
  .link-row { display: grid; grid-template-columns: 1fr 1fr; align-items: start; }
  .link-answer { grid-column: 1 / -1; }
}

/* Подписка. Своих цветов и рамок нет намеренно: это такая же карточка экрана настроек, и
   выделять её акцентом значило бы давить кнопкой продажи на человека каждый раз, когда он зашёл
   отключить источник. Красный в бренде дозирован (см. tokens.css). */
.plan-lines { display: flex; flex-direction: column; gap: var(--space-1); }
.plan-line { font-size: var(--fs-s); }
.plan-line.muted { color: var(--muted); }
.plan-actions { margin-top: var(--space-3); }

.source-status {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-size: var(--fs-xs); color: var(--muted); white-space: nowrap;
}
.status-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--border-strong); }
.source-status.on { color: var(--text); }
.source-status.on .status-dot { background: var(--accent); }

.primary {
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-m);
  background: var(--ink); color: var(--ink-on);
  font-weight: 600;
  transition: filter var(--dur) var(--ease), opacity var(--dur) var(--ease);
}
.primary:hover { filter: brightness(1.08); }
.primary:disabled { opacity: 0.5; cursor: default; }
/* ОПАСНАЯ КНОПКА — ТАКАЯ ЖЕ КОРОБКА, ЧТО И ОБЫЧНАЯ (22.08, слово владельца: «очень некрасиво
   подсвечивается кнопка при наведении курсора»). У `.ghost` есть отступы и скругление, а у этой
   не было ничего: подсветка ложилась голым прямоугольником вплотную к буквам. Значения те же, что
   у `.ghost`, — иначе две кнопки в одном меню стояли бы разной высоты. */
.danger-text {
  color: var(--danger); font-size: var(--fs-s);
  padding: var(--space-1) var(--space-2); border-radius: var(--radius-s);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.danger-text:hover { background: var(--danger-soft); color: var(--danger); }

.risk-panel {
  border: 1px solid var(--danger);
  border-radius: var(--radius-m);
  background: var(--danger-soft);
  padding: var(--space-3) var(--space-4);
  display: flex; flex-direction: column; gap: var(--space-2);
  margin-bottom: var(--space-3);
}
.risk-title { font-weight: 650; color: var(--danger); font-size: var(--fs-s); }
.risk-body { font-size: var(--fs-s); }
.risk-list { margin: 0 0 0 1.1em; font-size: var(--fs-s); color: var(--muted); }
.risk-list .risk-hard { color: var(--danger); }
.risk-ack { display: flex; align-items: center; gap: var(--space-2); font-size: var(--fs-s); cursor: pointer; }
.risk-ack input { accent-color: var(--danger); }

.qr-holder {
  display: grid; place-items: center;
  min-height: 220px;
  background: #fff;                 /* a QR must be dark-on-light to scan, in any theme */
  border-radius: var(--radius-m);
  padding: var(--space-3);
  margin-top: var(--space-2);
}
.qr-holder svg { width: 220px; height: 220px; display: block; }

.flow-form { display: flex; flex-direction: column; gap: var(--space-3); max-width: 22rem; }
.flow-label { display: flex; flex-direction: column; gap: var(--space-1); font-size: var(--fs-s); color: var(--muted); }
.flow-input {
  font: inherit; color: inherit;
  background: var(--raised);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-m);
  padding: var(--space-2) var(--space-3);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.flow-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.flow-error { font-size: var(--fs-s); color: var(--danger); min-height: 1.2em; }
.flow-buttons { display: flex; gap: var(--space-2); }
/* Поля формы отделены от её действия (11.09): между ними встаёт волосяная черта блока
   `.source-part`, как у тяжёлых действий источника, — поэтому поля держат свой ритм сами. */
.flow-form-fields { display: flex; flex-direction: column; gap: var(--space-3); }
/* ПУСТАЯ СТРОКА ОШИБКИ НЕ ЗАНИМАЕТ МЕСТО (11.09, вторая жалоба владельца: «отступ так и
   остался»). Абзац ошибки живёт в форме всегда — он `role="alert"` и обязан существовать ДО
   появления текста, иначе его не озвучат. Но пустым он стоил ДВАЖДЫ: `min-height: 1.2em`
   резервировал ему ~17px собственной высоты, и вдобавок форма — флексбокс с зазором, а зазор
   считается между КАЖДОЙ парой детей, так что пустой абзац съедал два зазора вместо одного.
   Вместе это ~29px из ~65px между «Паролем» и «Войти» — вот почему правка одних лишь отступов
   блока (16px→12px) была глазом незаметна.

   ОСОЗНАННЫЙ РАЗМЕН: `min-height` стоял, чтобы форма не дёргалась в момент появления ошибки.
   Постоянная пустота в 17px у каждого человека при каждом входе — дороже, чем разовый сдвиг
   кнопки у того, кто ошибся паролем. Появится текст — правило перестанет действовать само,
   высота вернётся, озвучивание не страдает. */
.flow-error:empty { display: none; }

/* Настройки человека (§18). Строки одинаковой высоты, управление справа — как на экране настроек
   телефона: человек ведёт взглядом по названиям, а не выискивает разнокалиберные поля. */
.prefs { display: flex; flex-direction: column; gap: var(--space-3); }
.prefs-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3); min-height: 2.5rem;
}
.prefs-name { font-size: var(--fs-s); }
/* ПОЯСНЕНИЯ ЧИТАЮТ, А НЕ УГАДЫВАЮТ (просьба владельца 16.08: «серый шрифт описание сделать
   светлее, у ГПТ он и побольше и лучше виден»). Было 12px при контрасте 4.89:1 — на пороге
   читаемости; стало 13.5px при 6.51:1. Цвет поднят в самом токене, поэтому вместе с сайтом
   светлеет и приложение: у них ОДНА палитра, и расходиться ей нельзя. */
.prefs-hint { font-size: var(--fs-s); color: var(--muted); margin-top: 2px; line-height: 1.45; }
/* ВКЛЮЧЁННЫЙ ПРОДУКТ, КОТОРЫЙ ВСЕГДА МОЛЧИТ, — единственный случай, где подсказка
   становится предупреждением: человек читает «включено» как «работает», и без этого
   различия строка потерялась бы среди остальных пояснений. */
.prefs-warn { color: var(--danger, var(--text)); font-weight: 600; }


/* «ПОДРОБНЕЕ» — ШАГ ВГЛУБЬ, А НЕ СТЕНА (волна 2, 16.08). Разбор не выброшен: он ждёт того, кому
   нужен, и не отнимает экран у того, кому не нужен. Заголовок раскрывашки — такой же вторичный
   текст, только кликабельный; ни рамки, ни фона — это строка, а не карточка. */
.prefs-more { margin-top: 2px; }
.prefs-more > summary {
  font-size: var(--fs-s); color: var(--muted); cursor: pointer; list-style: none;
  text-decoration: underline; text-underline-offset: 2px; width: max-content;
}
.prefs-more > summary::-webkit-details-marker { display: none; }
.prefs-more[open] > summary { margin-bottom: 2px; }
.prefs-select { min-width: 9rem; }
/* ВЫБОР ЧАТОВ: ШАПКА СТОИТ, СПИСОК ЛИСТАЕТСЯ (решение владельца 15.08). Прежде прокручивалось
   всё вместе — на третьем чате поиск и фильтры уезжали за край, и чтобы сменить фильтр, надо было
   листать обратно наверх. И padding по вертикали был нулевым: рамка поля поиска прилипала к рамке
   контейнера, а её красная обводка фокуса срезалась overflow — «рамка наезжает на рамку»
   (скриншоты владельца, 15.08). Высота поднята с 18rem: шапка теперь не скроллится, и прежний
   потолок оставил бы списку четыре строки. */
.prefs-chatlist { display: flex; flex-direction: column; max-height: 30rem;
                  border: 1px solid var(--border); border-radius: var(--radius-m);
                  padding: var(--space-3); }
.prefs-chatlist-head { flex: none; }
.prefs-chatlist-body { flex: 1; min-height: 0; overflow-y: auto;
                       /* обводке фокуса галочек и полю ручного ввода нужно место, иначе их
                          срежет той же гильотиной, что резала поиск */
                       padding: var(--space-1); margin: 0 calc(-1 * var(--space-1)); }
.prefs-row input[type="checkbox"] { width: 1.15rem; height: 1.15rem; accent-color: var(--accent); }
.prefs-row input[type="checkbox"]:disabled { opacity: 0.5; }
/* Складка: длинный список чатов не должен растягивать экран настроек — как переход на свой экран
   в приложении, только здесь он раскрывается на месте. */
/* Порядок — две кнопки в ряд: нажатие на выбранную переворачивает его, стрелка говорит куда. */
.prefs-order { display: flex; gap: var(--space-2); margin: var(--space-2) 0; }
.prefs-order-btn {
  flex: 1; padding: var(--space-2); border-radius: var(--radius-m);
  background: var(--raised); color: var(--muted); font-size: var(--fs-s);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
/* ВЫБРАННОЕ СОСТОЯНИЕ — ВСЕГДА ОДНИМ КРАСНЫМ (замечание владельца 15.08): галочки уже красные,
   а активная кнопка сегмента была серой — в одних местах выбор помечен акцентом, в других нет.
   Правило одно на весь сайт: выбранное = тонкая красная обводка на мягком красном поле. Рамка
   есть у ОБЕИХ кнопок (у неактивной — прозрачная), чтобы ряд не прыгал при переключении. */
.prefs-order-btn { border: 1px solid transparent; }
.prefs-order-btn.active { background: var(--accent-soft); color: var(--text); font-weight: 600;
                          border-color: var(--accent); }

.prefs-fold { border-top: 1px solid var(--border); padding-top: var(--space-2); }
.prefs-fold > summary { cursor: pointer; font-size: var(--fs-s); padding: var(--space-2) 0; }
.prefs-fold .prefs-chatlist { max-height: 30rem; }

/* Режим НАЗВАННОЙ группы — своя строка ПОД чатом, а не в его flex-ряду: вложенная в ярлык галочки
   разметка складывала кнопки и подсказку друг на друга (скриншот владельца, 15.08). Отступ слева
   говорит принадлежность строки чату над ней. */
.prefs-when { margin: 0 0 var(--space-2) var(--space-3); }
.prefs-when .prefs-order { margin: 0; }
.prefs-when .prefs-order-btn { flex: none; padding: var(--space-1) var(--space-3);
                               font-size: var(--fs-xs); }

/* КНОПКА ОБЯЗАНА ВЫГЛЯДЕТЬ КНОПКОЙ (замечание владельца 15.08). `.ghost` в настройках — это
   «Сохранить», «Удалить», «Выбрать всё показанное»: голый серый текст читался как подпись.
   Рамка — только в настройках: в шапках и списках ghost остаётся тихим, там он — навигация. */
.settings-view .prefs .ghost, .settings-view .prefs-chatlist .ghost, .event-reasons .ghost {
  border: 1px solid var(--border-strong);
}
.settings-view .prefs .ghost:hover, .settings-view .prefs-chatlist .ghost:hover {
  border-color: var(--text);
}
.prefs-stack { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-2); }

/* Выдача поиска поясов. Раньше здесь стоял `select size=8` со всеми четырьмя сотнями названий —
   на телефоне пол-экрана у каждого, хотя пояс определяется сам и правит его один из сотни.
   Строк не больше восьми по построению, поэтому высоту не ограничиваем: список кончается сам. */
.tz-list { display: flex; flex-direction: column; }
.tz-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  width: 100%;
  padding: 10px 12px;
  border: 0;
  border-radius: var(--radius-s, 10px);
  background: transparent;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.tz-row:hover,
.tz-row:focus-visible { background: var(--raised); }
.tz-name { overflow-wrap: anywhere; }
/* Сдвиг не должен ужиматься: он короткий и он — то, по чему человек узнаёт свой пояс. */
.tz-offset { flex: 0 0 auto; color: var(--muted); font-size: var(--fs-s); }

/* a quiet «working» line for the moment between a click and the first server answer — so a pairing
   panel never sits blank (the empty red box the MAX connect showed). */
.flow-wait {
  display: flex; align-items: center; gap: var(--space-2);
  font-size: var(--fs-s); color: var(--muted); padding: var(--space-2) 0;
}
.flow-wait::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: var(--accent); animation: pulse 1.2s var(--ease) infinite;
}

/* in-app confirmation — the native dialog names the domain and reads like a security warning */
.modal-overlay {
  position: fixed; inset: 0; z-index: 40;
  display: grid; place-items: center;
  background: rgba(0, 0, 0, 0.45);
  padding: var(--space-4);
}
.modal {
  background: var(--raised);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-l);
  box-shadow: var(--shadow-2);
  padding: var(--space-5);
  max-width: 26rem; width: 100%;
  display: flex; flex-direction: column; gap: var(--space-4);
}
.modal-text { font-size: var(--fs-base); }
.modal-actions { display: flex; justify-content: flex-end; gap: var(--space-2); }

/* toast */
.toast {
  position: fixed; bottom: var(--space-5); left: 50%; transform: translateX(-50%);
  background: var(--raised); color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-m);
  padding: var(--space-2) var(--space-4);
  font-size: var(--fs-s);
  box-shadow: var(--shadow-2);
  z-index: 10;
}

/* scrollbars */
.thread, .conv-list { scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
.thread::-webkit-scrollbar, .conv-list::-webkit-scrollbar { width: 8px; }
.thread::-webkit-scrollbar-thumb, .conv-list::-webkit-scrollbar-thumb {
  background: var(--border-strong); border-radius: 4px;
}

/* ── responsive ────────────────────────────────────────────────────────────── */

.nav-toggle {
  display: none;
  position: fixed; top: var(--space-3); left: var(--space-3); z-index: 30;
  width: 40px; height: 40px; border-radius: var(--radius-m);
  background: var(--surface); border: 1px solid var(--border);
  font-size: 1.1rem;
}

/* desktop: fold the sidebar to a zero column so the reading pane takes the full width; the
   floating ☰ (a shell sibling) appears only while collapsed and expands it again. */
@media (min-width: 861px) {
  .shell.nav-collapsed { grid-template-columns: 0 1fr; }
  .shell.nav-collapsed .sidebar {
    transform: translateX(-100%);
    border-right: none;
    overflow: hidden;
  }
  /* ДВЕРЬ В ОДИН КОНЕЦ — ЖИВОЙ ОТКАЗ 22.08: «пропала кнопка меню слева, и она появляется только
     если сузить экран».
     ЧТО БЫЛО. 19.08 плавающий ☰ убрали здесь по просьбе владельца («лез в глаза»), рассудив, что
     развернуть панель есть чем — её собственной кнопкой ««» в шапке. Но эта кнопка живёт ВНУТРИ
     панели и уезжает вместе с ней (`transform: translateX(-100%)` выше). Значит на широком экране
     свёрнутая панель не разворачивалась НИЧЕМ: состояние помнится в localStorage и переживает
     перезагрузку, и единственным выходом было сузить окно — там работает телефонный ☰. Код и стиль
     при этом расходились: обработчик в `shell.js` до сих пор написан так, будто на компьютере ☰
     показывается в свёрнутом состоянии («on desktop the ☰ appears only while collapsed → expand»).
     ПОЧЕМУ НЕ ВЕРНУЛИ ПРЕЖНИЙ КВАДРАТ. Претензия 19.08 остаётся верной: карточка поверх страницы
     мозолила глаза ВСЕГДА — и когда панель открыта, и когда закрыта. Теперь кнопка есть ровно в
     том состоянии, где она единственный вход, и выглядит тихо: без подложки и рамки, меньше
     размером — так это сделано у ChatGPT и Claude. Развёрнутая панель по-прежнему её не показывает.
     МЕСТО ВЫБРАНО ПО АРИФМЕТИКЕ, А НЕ НА ГЛАЗ: колонка чтения (46rem) и колонка настроек (40rem)
     стоят по центру, и слева от них на самом узком настольном окне (861px) остаётся ~46px. Кнопка
     32px при отступе 8px укладывается в них, то есть не накрывает ни текст, ни «← Настройки». */
  .shell.nav-collapsed ~ .nav-toggle {
    display: grid; place-items: center;
    top: var(--space-2); left: var(--space-2);
    width: 32px; height: 32px;
    background: none; border: none; color: var(--muted);
  }
  .shell.nav-collapsed ~ .nav-toggle:hover { background: var(--hover); color: var(--text); }
}

@media (max-width: 860px) {
  .shell { grid-template-columns: 1fr; }
  .nav-toggle { display: grid; place-items: center; }
  .sidebar-collapse { display: none; }   /* mobile uses the drawer, not the desktop fold */
  .sidebar {
    /* ОДНА ШИРИНА НА ВСЕ ЭКРАНЫ (14.08, жалоба владельца: «на разных размерах страницы левая часть
       выглядит по-разному, хочется аккуратно и маленько, без огромных пустых мест»).
       Здесь стояло `inset: 0 25% 0 0` — то есть панель занимала 75% ЛЮБОГО экрана. На телефоне это
       выглядит нормально, а на узком окне ноутбука (и на планшете) превращается в широкую полосу с
       пустотой справа от названий: те же разговоры, что на большом экране умещаются в 288 пикселей.
       Теперь ширина ОДНА и та же, что у настольной панели, и лишь на совсем узких экранах ужимается,
       оставляя краешек содержимого — по нему видно, что панель поверх, а не вместо. */
    position: fixed; inset: 0 auto 0 0; width: min(var(--sidebar-w), 86vw); z-index: 20;
    transform: translateX(-100%);
    transition: transform var(--dur) var(--ease);
    box-shadow: var(--shadow-2);
  }
  .shell.nav-open .sidebar { transform: translateX(0); }
  .thread { padding-top: calc(var(--space-6) + var(--space-4)); }
  /* НАСТРОЙКИ И СОБЫТИЯ — ТОЖЕ ПОД ☰ (09.08). Отступ добавили только ленте разговора, а плавающий
     ☰ лежит поверх ВСЕГО: на настройках он накрывал кнопку «← К разговорам», и человек, нажимая
     «назад», открывал панель вместо возврата. Выхода с экрана фактически не было. */
  .settings-view { padding-top: calc(var(--space-6) + var(--space-4)); }
  /* ПОДЛОЖКА ПОД ПАНЕЛЬЮ: закрыть её, ничего не выбрав, можно было только тем же ☰ — касание мимо
     не работало. Так делают Gmail и ChatGPT: затемнение, и оно же закрывает. */
  .shell.nav-open::after {
    content: "";
    position: fixed; inset: 0; z-index: 19;
    background: rgba(0, 0, 0, 0.4);
  }
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
  .thread { scroll-behavior: auto; }
}

/* MAX pairing: two ways in on one screen, because to the user they are one decision — they either
   have the app or they do not. The separator says so without making them choose a mechanism. */
.max-or {
  margin: 18px 0 14px;
  text-align: center;
  color: var(--muted);
  font-size: 13px;
  position: relative;
}
.max-or::before,
.max-or::after {
  content: "";
  position: absolute;
  top: 50%;
  width: calc(50% - 28px);
  height: 1px;
  background: var(--line);
}
.max-or::before { left: 0; }
.max-or::after { right: 0; }

.max-phone .flow-form { margin-top: 0; }

/* Восемь символов, которые человек переписывает РУКАМИ с одного экрана на другой и не может
   вернуться проверить. Поэтому крупно, моноширинно и с разрядкой: «0» и «O», «1» и «l» в обычном
   шрифте различаются плохо, а цена ошибки — заново пройденная привязка. Выделяются одним движением
   (`user-select: all`) — на телефоне, где эта дверь и нужна, точное выделение пальцем мучительно. */
.link-code {
  margin: 14px 0 4px;
  text-align: center;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 30px;
  letter-spacing: 0.22em;
  text-indent: 0.22em;          /* компенсирует разрядку последнего символа — иначе текст «уезжает» */
  font-variant-numeric: slashed-zero tabular-nums;
  color: var(--text);
  user-select: all;
  -webkit-user-select: all;
}

/* Адрес вебхука и проверочное слово для кабинета Meta. НЕ `.link-code`: тот про восемь символов,
   которые переписывают руками, и потому крупный и разрежённый. Здесь наоборот — строку набирать не
   надо и нельзя, зато она длинная, и её задача поместиться на телефоне, не разорвав карточку. */
.flow-copy-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-top: var(--space-2);
  flex-wrap: wrap;
}
.flow-copy-text { min-width: 0; flex: 1 1 auto; }   /* без этого длинный URL распирает строку */
.copy-value {
  margin: 2px 0 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--fs-s);
  color: var(--text);
  overflow-wrap: anywhere;
  user-select: all;
  -webkit-user-select: all;
}

/* ── auth providers on the login card (§3) ──────────────────────────────────── */
.login-form { display: flex; flex-direction: column; gap: var(--space-2); width: 100%; }
.login-field-label {
  display: flex; flex-direction: column; gap: var(--space-1);
  font-size: var(--fs-s); color: var(--muted); text-align: left;
}
.login-input {
  font: inherit; color: inherit; background: var(--raised);
  border: 1px solid var(--border-strong); border-radius: var(--radius-m);
  padding: var(--space-3) var(--space-4);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.login-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.login-input::placeholder { color: var(--muted); }
.login-primary { width: 100%; text-align: center; padding: var(--space-3) var(--space-4); }
.gsi-host { min-height: 44px; display: grid; place-items: center; width: 100%; }
.login-or {
  position: relative; width: 100%; margin: var(--space-2) 0;
  text-align: center; color: var(--muted); font-size: var(--fs-s);
}
.login-or::before, .login-or::after {
  content: ""; position: absolute; top: 50%; width: calc(50% - 1.4em); height: 1px; background: var(--border);
}
.login-or::before { left: 0; }
.login-or::after { right: 0; }
.login-links, .login-actions {
  display: flex; gap: var(--space-2); width: 100%; justify-content: center;
}
.login-actions { flex-direction: column; }
.login-ghost {
  font-size: var(--fs-s); color: var(--muted);
  padding: var(--space-1) var(--space-2); border-radius: var(--radius-s);
}
.login-ghost:hover { background: var(--hover); color: var(--text); }
.login-email { font-weight: 650; color: var(--text); }

/* Sign in with Apple — the guideline button (black, Apple mark + label); hairline defines it on
   any theme. */
.apple-btn {
  width: 100%;
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  background: #000; color: #fff; border: 1px solid var(--border-strong);
  border-radius: var(--radius-m); padding: var(--space-3) var(--space-4);
  font-weight: 600; font-size: var(--fs-base);
  transition: filter var(--dur) var(--ease);
}
.apple-btn:hover { filter: brightness(1.4); }
.apple-glyph { display: inline-flex; width: 15px; height: 18px; margin-top: -2px; }
.apple-glyph svg { width: 100%; height: 100%; display: block; }

/* account management (§3.5): sign-in methods + delete, on the settings screen */
.account-methods { display: flex; flex-direction: column; gap: var(--space-1); margin: var(--space-2) 0; }
.account-method {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);
  padding: var(--space-2) var(--space-3); border-radius: var(--radius-s);
  background: var(--raised); font-size: var(--fs-s);
}
.account-method-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.account-delete { margin-top: var(--space-3); }

/* Строка-переход в настройках: за ней ЭКРАН, а не содержимое — поэтому шеврон и указатель. */
.settings-nav { cursor: pointer; }
.settings-nav:hover { border-color: var(--accent); }
.settings-nav .source-head { align-items: center; }
.settings-nav-chevron { color: var(--muted); font-size: 1.25rem; line-height: 1; }
.settings-nav-side { display: flex; align-items: center; gap: var(--space-3); }

/* БЛОК ВНУТРИ КАРТОЧКИ ИСТОЧНИКА (31.08): «что читать» и тяжёлые кнопки отделены чертой, чтобы
   в объединённой вкладке было видно, где кончается один источник и начинается следующий. */
.source-part { margin-top: var(--space-4); padding-top: var(--space-4);
               border-top: 1px solid var(--border); }
/* ДЕЙСТВИЕ ФОРМЫ СТОИТ БЛИЖЕ, ЧЕМ БЛОК ИСТОЧНИКА (11.09, слово владельца: «кнопка ВОЙТИ далеко
   находится от строки ПАРОЛЬ»). Двойной отступ `.source-part` разделяет РАЗНЫЕ темы внутри
   карточки — там это правильно. Но «Войти» принадлежит той же форме, что и поля над ней: черта
   нужна, чтобы кнопка не читалась как часть поля пароля, а не чтобы оторвать её от него. */
.source-part-snug { margin-top: var(--space-2); padding-top: var(--space-3); }
.source-part-name { font-size: var(--fs-m); font-weight: 600; margin-bottom: var(--space-2); }
.source-part-heavy .source-part-name { color: var(--muted); }

/* КРЕСТИК ВНУТРИ ПОЛЯ (31.08): стереть длинное указание одним нажатием, а не выделять его
   пальцем. Поле остаётся обычным — крестик лежит поверх, у правого края. */
.field-with-clear { position: relative; display: flex; align-items: center; }
.field-with-clear .flow-input { width: 100%; padding-right: 2.2rem; }
.field-clear {
  position: absolute; right: var(--space-2);
  border: 0; background: none; cursor: pointer; line-height: 1;
  font-size: 1.25rem; color: var(--muted); padding: 0 var(--space-1);
}
.field-clear:hover { color: var(--text); }

/* add a sign-in method (§3.5): widgets stack; the email sub-form is a compact inline row that wraps */
.account-add { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-2);
  margin-top: var(--space-2); padding-top: var(--space-3); border-top: 1px solid var(--border); }
.account-add .apple-btn, .account-add .gsi-host { max-width: 18rem; }
.account-add-email { width: 100%; max-width: 20rem; display: flex; flex-direction: column; gap: var(--space-2); }
.account-add-form { display: flex; flex-wrap: wrap; gap: var(--space-2); width: 100%; }
.account-add-form .login-input { flex: 1; min-width: 9rem; }

/* step-up proof panel (§3.5): login's provider controls, collected inline for delete / merge */
.reauth { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-2);
  margin-top: var(--space-2); padding-top: var(--space-3); border-top: 1px solid var(--border); }
.reauth .apple-btn, .reauth .gsi-host, .reauth .login-widget { max-width: 18rem; }
.reauth-prompt { font-size: var(--fs-s); color: var(--muted); margin-bottom: var(--space-1); }
.reauth-cancel { margin-top: var(--space-1); }
.account-merge { margin-top: var(--space-3); align-self: flex-start; }

/* ── события (§20) ────────────────────────────────────────────────────────────────────────────
   Карточка события — такая же карточка экрана, как источник и подписка: своих цветов у неё нет.
   Красный в бренде дозирован, и красить им «не надо» значило бы делать отказ громче согласия —
   а это ровно та кнопка, которую человек нажимает чаще (замер 08.08: подтверждено 9 из 687). */
.events-feed { display: flex; flex-direction: column; gap: var(--space-3); }

.event-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-l);
  padding: var(--space-4) var(--space-5);
  box-shadow: var(--shadow-1);
}
.event-head { display: flex; flex-direction: column; gap: var(--space-1); }
.event-title { font-size: var(--fs-l); font-weight: 650; }
.event-line { font-size: var(--fs-s); color: var(--muted); }

/* ЗНАЧКИ СТРОК КАРТОЧКИ — ТЕ ЖЕ, ЧТО В ПРИЛОЖЕНИИ (22.08, просьба владельца).
   Значок стоит в КОЛОНКЕ фиксированной ширины (16px, как в приложении): `Label`-подобная посадка
   вплотную сдвигает тексты друг относительно друга, и строки едут — это уже чинили на телефоне.
   Цвет несёт ЗНАЧЕНИЕ, а не украшает, поэтому он свой у каждого смысла и объявлен токенами:
   в тёмной теме те же роли берут более светлые оттенки, иначе значки тонут в фоне. */
.event-line-icon {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-s, 8px);
}
.event-icon {
  flex: 0 0 16px;
  width: 16px;
  height: 16px;
  margin-top: 1px;                 /* к первой строке текста, а не к середине абзаца */
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.event-icon svg { width: 15px; height: 15px; display: block; }
.event-icon-clock    { color: #2f6fd0; }
.event-icon-bell     { color: #d98324; }
.event-icon-place    { color: #d1508a; }
.event-icon-chat     { color: #2f9e5e; }
.event-icon-found    { color: #7b5cd6; }
.event-icon-calendar { color: var(--accent); }
.event-icon-quote    { color: var(--muted); }
/* «Исходник» — сворачиваемый: значок стоит в той же колонке, что у строк выше, иначе шапка
   выпадает из ряда, и это первое, что видно глазом (та же теснота, что чинили на телефоне). */
.event-quote > summary { display: flex; align-items: center; gap: var(--sp-s, 8px); }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .event-icon-clock    { color: #6ea8f5; }
  :root:not([data-theme="light"]) .event-icon-bell     { color: #f0a95c; }
  :root:not([data-theme="light"]) .event-icon-place    { color: #f07fb0; }
  :root:not([data-theme="light"]) .event-icon-chat     { color: #5fce8d; }
  :root:not([data-theme="light"]) .event-icon-found    { color: #a68df0; }
}
:root[data-theme="dark"] .event-icon-clock    { color: #6ea8f5; }
:root[data-theme="dark"] .event-icon-bell     { color: #f0a95c; }
:root[data-theme="dark"] .event-icon-place    { color: #f07fb0; }
:root[data-theme="dark"] .event-icon-chat     { color: #5fce8d; }
:root[data-theme="dark"] .event-icon-found    { color: #a68df0; }
.event-source { color: var(--muted); }
.event-link { margin-top: var(--space-1); }
/* ВОЗДУХ МЕЖДУ ДВУМЯ ТИХИМИ КНОПКАМИ (10.09, слово владельца: «немного пространства нужно»). С рамками
   «Открыть сообщение» и «Исходник» стали кнопками, и две кнопки вплотную читаются одной сдвоенной.
   Зазор — из той же шкалы, что между кнопками действий ниже. */
.event-quote { margin-top: var(--space-2); }

.event-state {
  font-size: var(--fs-xs); color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.04em;
}
/* Единственное место, где событие получает акцент, — то, что ждёт решения. Всё остальное
   состояние человек читает словами, а не цветом. */
.event-state-pending { color: var(--accent); }

.event-actions { display: flex; gap: var(--space-2); margin-top: var(--space-3); }
.event-card.event-done, .event-card.event-dismissed { opacity: 0.72; }

/* КАРТОЧКА УХОДИТ В СВОЙ РАЗДЕЛ ДВИЖЕНИЕМ, А НЕ ИСЧЕЗНОВЕНИЕМ (26.08). Решение принято — список
   сейчас перестроится, и человек должен УВИДЕТЬ, что именно ушло: без этих 180 мс перестройка
   читается как мигание, и глазу приходится заново искать, где он читал. Длительность совпадает с
   паузой в коде — они одно и то же движение, описанное с двух сторон. */
.event-card.event-leaving {
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity 180ms ease, transform 180ms ease;
}
@media (prefers-reduced-motion: reduce) {
  .event-card.event-leaving { transition: none; }
}

.events-empty { padding: var(--space-5) 0; }
.events-empty-title { font-size: var(--fs-l); font-weight: 650; margin-bottom: var(--space-1); }

/* Причина отказа. Кнопки в столбик и без акцента: человек уже отказался, и это уточнение, а не
   второй вопрос. «Пропустить» — такая же кнопка, чтобы отказ от объяснения не выглядел хуже. */
.event-reasons { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-1); margin-top: var(--space-3); }
.event-reasons button { text-align: left; }
.event-calendar { margin-top: var(--space-2); }

/* Правовые страницы в подвале НАСТРОЕК (22.08). Та же серая строка, что под входом. */
.settings-legal { font-size: var(--fs-xs); color: var(--muted); text-align: center;
                  margin: var(--space-4) 0 var(--space-2); }
.settings-legal a { color: var(--muted); text-decoration: underline; }

/* ── внешний вид: образцы акцента (15.08) ───────────────────────────────────────────────────── */
.accent-row { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
              margin: var(--space-2) 0; }
.accent-swatch {
  width: 2rem; height: 2rem; border-radius: 50%;
  background: var(--swatch); border: 2px solid transparent;
  transition: transform var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.accent-swatch:hover { transform: scale(1.08); }
/* Выбранный образец — та же грамматика, что у всего выбранного: обводка цветом самого выбора,
   с зазором, чтобы кольцо читалось на любом фоне. */
.accent-swatch.active { border-color: var(--swatch); box-shadow: inset 0 0 0 2px var(--bg); }
.accent-custom {
  width: 2rem; height: 2rem; padding: 0; border: 1px dashed var(--border-strong);
  border-radius: 50%; background: none; cursor: pointer;
}

/* Заголовок группы настроек дышит: подряд идущие «имя, подсказка, поле, имя, подсказка…»
   читались сплошником (замечание владельца 15.08) — теперь у каждой группы отступ сверху. */
.prefs > .prefs-name { margin-top: var(--space-3); }
.prefs > .prefs-name:first-child { margin-top: 0; }

/* ГРУППЫ ВНУТРИ ОДНОГО ЭКРАНА (просьба владельца 15.08: «раздел идёт сплошником»). Тонкая черта
   и заголовок отделяют «меня нет» от «тем», а темы — от журнала: человек видит, где кончается
   один вопрос и начинается другой, не читая всё подряд. Так разделяют длинные экраны настроек
   Apple и Google — линией и подзаголовком, а не пустотой. */
.prefs-group { margin-top: var(--space-5); padding-top: var(--space-4);
               border-top: 1px solid var(--border); }
.prefs-group:first-child { margin-top: 0; padding-top: 0; border-top: none; }
.prefs-group-title { font-size: var(--fs-base); font-weight: 600; margin: 0; }
.prefs-group .prefs-hint { margin-top: var(--space-1); }

/* ПОДВАЛ ПАНЕЛИ — ОДНА НАСТОЯЩАЯ КНОПКА (решение владельца 15.08). Здесь стояла шестерёнка
   размером с подпись рядом с «EN» и «Выйти»: значок не читался как кнопка, а необратимый выход
   лежал в двух сантиметрах от переключателя языка. Язык и выход уехали в «Аккаунт»; оставшаяся
   кнопка получила подпись словом, размер пальца и рамку — как в панели ChatGPT. */
.sidebar-foot { justify-content: stretch; }
.sidebar-settings {
  flex: 1; display: flex; align-items: center; gap: var(--space-2);
  min-height: 44px; padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border); border-radius: var(--radius-m);
  color: var(--text); font-size: var(--fs-s);
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.sidebar-settings:hover { background: var(--hover); border-color: var(--accent); }
.sidebar-settings-glyph { font-size: 1.1rem; line-height: 1; }

/* Значок в строке меню: размер под подпись, цвет — от текста. Красная строка «Удалить» красит и
   свой значок сама, без отдельного правила (просьба владельца 15.08, как в меню ChatGPT). */
.conv-menu-item { display: flex; align-items: center; gap: var(--space-3); }
.conv-menu-icon { flex: none; width: 18px; height: 18px; opacity: 0.85; }
.conv-menu-icon svg { width: 100%; height: 100%; display: block; }

/* ЗНАК ИСТОЧНИКА ПОД ОТВЕТОМ — МЕЛКИЙ И В СТРОКУ (12.09). Общий `.source-mark` собран под сетку
   экрана настроек (28px и `grid-row`), и в строке улики он развалил бы её. Здесь знак —
   спутник слова, а не самостоятельный элемент: по высоте строки и с базовой линией. */
.ev-meta .ev-mark { width: 14px; height: 14px; border-radius: 3px; grid-row: auto;
  vertical-align: -2px; margin-right: 5px; }
.ev-meta .ev-mark.mark-bmstu, .ev-meta .ev-mark.mark-university,
.ev-meta .ev-mark.mark-ximss { width: 16px; height: 16px; border-radius: 0; }
