:root {
  /* ТЁМНЫЙ ОБЛИК СТРАНИЦЫ. Не украшение: этим лечатся СВЕТЛЫЕ СИСТЕМНЫЕ ПОЛОСЫ
     на айфоне — серая плашка с часами сверху и белая полоса со стрелками и
     «галочкой» над клавиатурой. Обе рисует система, и вид она берёт из облика
     веб-приложения; пока страница не объявила тёмную схему, она считается
     светлой. Действует независимо от «нового вида»: это починка бага, а не
     оформление. Дубль — в index.html (первый кадр) и метой color-scheme. */
  color-scheme: dark;
  /* Палитра «Ночной перламутр». Имя --orange оставлено прежним (на него
     завязаны все акценты по коду) — теперь это перивинклевый акцент. */
  --bg: #0E0E10;
  --orange: #8AA0F0;
  --text: #C9C9CF;
  --bright: #F2F2F4;
  --dim: #8B8B93;
  --green: #4ADE80;
  --red: #ff453a;
  --panel: #17171A;
  --border: #292930;
  /* Мягкая подложка акцента (перивинкль на ~16% непрозрачности) — свои пузыри,
     активные кнопки-варианты, выбранные чипы фильтра. Единый холодный тон. */
  --accent-soft: rgba(138, 160, 240, .16);
  /* ══════════ ПОЛНАЯ ВЫСОТА ЭКРАНА — ОДНО ЧИСЛО НА ВСЁ ПРИЛОЖЕНИЕ ══════════
     Засада, ради которой это заведено. В приложении-иконке, которому разрешено
     заходить ПОД полосу с часами (мета статус-бара = black-translucent), айфон
     ЗАНИЖАЕТ высоту, которую сообщает странице, ровно на высоту этой полосы.
     Замеры с телефона владельца: экран 912, а «высота окна» — 844, то есть на
     68 меньше, и ровно 68 — верхнее безопасное поле.
     ВАЖНО: занижено только СООБЩАЕМОЕ число, а не сам холст страницы. Проверено
     наглядно: страницу временно покрасили в красный — краска видна до самого
     нижнего края экрана. Низ экрана странице ДОСТУПЕН, рисовать там можно.
     Занижены не все единицы высоты, а только часть. Замеры того же телефона:
       vh  = 912   lvh = 912   ← честные, равны экрану
       dvh = 844   svh = 844   ← занижены на 68 (и высота окна из кода тоже)
     Отсюда лечение: раскладка стоит на 100vh. Никакой самодельной прибавки
     сверху НЕ добавляем — раньше здесь была измеренная «недостача» (--app-gap),
     она давала 912 + 68 = 980 при экране 912, и нижние 68 точек (нижний ряд
     капсулы ввода: ＋, «Голос», микрофон, отправка) уезжали за край. Компенсация
     снята целиком — вместе с ней ушёл и перекос всплывающих подтверждений.

     ПОЧЕМУ НЕ dvh. dvh задуман как «высота с учётом сворачивающихся панелей
     браузера» и в обычном Safari он правильный. Но в установленном приложении с
     заходом под полосу часов он теряет верхнее безопасное поле. Панелей браузера
     в приложении-иконке нет вовсе, поэтому vh и dvh обязаны совпадать — и там,
     где они расходятся, верить надо vh.

     ПОЧЕМУ ПОЛЕ СВЕРХУ ЗДЕСЬ ЧИСЛОМ (--safe-top), А НЕ env(safe-area-inset-top).
     Значение переменной подставляется в свойство как есть, и env() внутри такой
     подстановки на телефоне владельца НЕ раскрывается — слагаемое молча
     выпадает. Поэтому поле МЕРЯЕТСЯ (невидимая метка в первом скрипте
     index.html, у неё env() стоит прямо в правиле — там он раскрывается всегда)
     и кладётся сюда уже в точках. Пересчитывается при повороте экрана и
     возврате из фона (js/ui.js). Само поле сверху к высоте раскладки отношения
     не имеет: на нём стоят отступ шапки и высота полоски под часами.

     НИЖНЕЕ безопасное поле (полоска жеста «домой», 34 точки) в раскладку тоже не
     входит: раскладка — весь экран, а от нижнего края отступают уже отдельные
     элементы через env(safe-area-inset-bottom) прямо в своих правилах. */
  --safe-top: 0px;
  /* ПРОВЕРЕНО НА ТЕЛЕФОНЕ ВЛАДЕЛЬЦА, НЕ МЕНЯТЬ БЕЗ НОВЫХ ЗАМЕРОВ.
     Раскладка = ВЫСОТА ОКНА (dvh), а не высота экрана (vh). Экран 912, окно 844.
     Пробовали 100vh (912) — числа выходили красивые (низ капсулы 874 при экране
     912), а НА ЭКРАНЕ капсулу срезало: телефон показывает странице только верхние
     844 точки, нижние 68 содержимому недоступны.
     ОСТОРОЖНО С «ДОКАЗАТЕЛЬСТВОМ ЧЕРЕЗ ЦВЕТ»: если покрасить страницу целиком,
     краска видна до самого низа экрана — но это НЕ значит, что туда можно положить
     содержимое. Телефон растягивает фон страницы на весь холст, в том числе на
     недоступную полосу. На этом опыте мы уже один раз ошиблись.
     ЧЕМ ЭТО ЧИСЛО ОСТАЛОСЬ. Раскладка на нём БОЛЬШЕ НЕ СТОИТ: и высота тела, и
     высота обёртки экрана заданы гранями (см. body и #app-shell ниже), и полную
     высоту телефон теперь отдаёт. Число живёт только как ПОТОЛОК высоты у двух
     всплывающих панелей (выбор проекта, словарь) — там заниженная оценка безвредна:
     панель просто не станет выше окна. */
  --app-h: 100dvh;
  /* ФОРМА ВСПЛЫВАЮЩЕЙ ПАНЕЛИ — ОДНО ЧИСЛО НА ВСЁ ПРИЛОЖЕНИЕ.
     Снято со снимка системного контекстного меню айфона, который прислал
     владелец со словами «хочу так везде». Его берут все всплывающие поверхности
     разом: окна и меню (.modal) и карточка вариантов ответа (.qbar). Меняется в
     одном месте — меняется везде. */
  --pop-radius: 14px;
  /* Ширина выдвижной панели бокового меню — примерно три четверти экрана. На это
     же число сдвигается основной экран, поэтому справа остаётся видимая полоска
     (390px → панель 296, полоска 94; 320px → панель 243, полоска 77). */
  --km-w: 76vw;
  /* НИЖНЕЕ поле уехавшего экрана — от нижней грани области приложения, которая в
     приложении-иконке совпадает с нижним краем экрана айфона. */
  --km-pad: 11px;
  /* ВЕРХНЕЕ поле. Считается ОТ ФИЗИЧЕСКОГО края экрана, а не от края области
     приложения, — это и есть то, что видит глаз.
     Суть засады: мета статус-бара стоит в «default» (иначе внизу вылезала
     фантомная полоса ~150px, см. историю правок), а при таком значении система
     НЕ пускает страницу под полосу с часами: область приложения начинается уже
     ПОД ней, и env(safe-area-inset-top) равен нулю. То есть к любому нашему
     верхнему полю сверху молча прибавляется системная полоса, а снизу такой
     прибавки нет — поля, равные внутри области приложения, на экране выглядят
     разными (сверху далеко, снизу вплотную).
     Поэтому верхнее поле НИКОГДА не превышает того, что система уже добавила
     сама: min(поле, safe-area-inset-top).
     — Полоса с часами ВНЕ области приложения (safe-area-inset-top = 0, текущий
       случай на телефоне Дмитрия): наше поле = 0, карточка поднята вплотную к
       верхней грани области — верхний зазор уменьшен до предела, доступного
       странице.
     — Полоса ВНУТРИ области (safe-area-inset-top ≈ 47–59, если мету когда-нибудь
       вернут на black-translucent): поле = 11, и тогда зазоры сверху и снизу
       равны уже по-настоящему, по физическим краям экрана.
     Срез сверху при этом никогда не достаёт до содержимого шапки: он не больше
     safe-area-inset-top, а ровно на эту величину у шапки стоит верхний отступ.
     Значит круглая кнопка слева не режется НИ ПРИ КАКОМ значении безопасной зоны
     — без сдвигов содержимого (прежний --km-drop больше не нужен).
     ПОЛЕ БЕРЁМ ЧИСЛОМ (--safe-top), а не env(safe-area-inset-top): внутри
     подстановки переменной env() на телефоне владельца не раскрывается (см.
     объяснение у --app-h выше), и поле молча оставалось бы нулевым. */
  --km-pad-top: min(var(--km-pad), var(--safe-top));
  --km-r: 20px;
  /* Единый ход открытия/закрытия: экран, панель (параллакс) и тень идут одним
     временем и одной кривой — мягкое замедление, без рывков. То же число
     держит menu.js (ANIM_MS). */
  --km-ms: 280ms;
  --km-ease: cubic-bezier(.22, .61, .36, 1);
}

* { box-sizing: border-box; }
/* Масштабирование выключено: manipulation убирает зум двойным тапом (оставляя
   обычную прокрутку), а щипок двумя пальцами гасится в app.js (noZoom). */
html, body { margin: 0; height: 100%; touch-action: manipulation; }
/* ХОЛСТ — ЛЕЧЕНИЕ МЁРТВОЙ ПОЛОСЫ ВНИЗУ.
   Телефон отдаёт странице 844 точки из 912: нижние 68 содержимому недоступны
   (проверено замерами, см. --app-h выше). Убрать полосу нельзя, но можно сделать
   её НЕВИДИМОЙ: фон самой страницы телефон растягивает на ВЕСЬ холст, включая
   недоступную полосу. Красим холст в тот цвет, каким фон приложения заканчивается
   внизу, — и граница пропадает. Значение подменяет ветка перламутра ниже. */
html { background: var(--canvas-bg, #0E0E10); }
body {
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, system-ui, "Segoe UI", Roboto, sans-serif;
  -webkit-tap-highlight-color: transparent;
  display: flex;
  flex-direction: column;
  /* ЗАПАС ПРОКРУТКИ У ДОКУМЕНТА — ОБЩИЙ ДЛЯ ВСЕХ ЭКРАНОВ.
     Раньше здесь стояла высота ровно в окно (--app-h = 844), и запас был заведён
     ТОЛЬКО в чате. Отсюда и брались мёртвые 68 точек внизу на главном, в боковом
     меню и в настройках: полную высоту (912) телефон отдаёт лишь СВОБОДНОЙ,
     ПРОКРУЧИВАЕМОЙ странице, а страница ровно в окно считается запертой — и грань
     bottom:0 у любого прибитого элемента (панель меню, тень, затемнение, обёртка)
     встаёт на 844, а не на нижний край экрана.
     Запас НЕВИДИМ: всё видимое прибито (#app-shell, полоска под часами, панель
     меню с тенью и затемнением, окна и подтверждения), поэтому прокрутка документа
     не двигает ни одного пикселя, а прокрутка внутри экрана наружу не передаётся
     (overscroll-behavior у .screen ниже).
     СЧИТАЕТСЯ ОТ vh, А НЕ ОТ dvh: dvh на этом телефоне и есть заниженное число
     (844), запас от него то появлялся бы, то исчезал вслед за решением телефона.
     vh честный (912), поэтому 912 + поле заведомо больше любой выданной высоты.
     Нижний предел 16 точек — на случай, если верхнего безопасного поля нет вовсе.
     ПАРА К ЭТОМУ ПРАВИЛУ — посадка #app-shell на экран (position:fixed ниже).
     Одно без другого не работает. */
  min-height: 100vh;
  min-height: calc(100vh + max(var(--safe-top, 0px), 16px));
  overflow-x: hidden;   /* экран при свайпе-назад уезжает вправо — без гориз. полосы */
}

/* ══════════ УДЕРЖАНИЕ НА КНОПКЕ — ЭТО НАЖАТИЕ, А НЕ ВЫДЕЛЕНИЕ ТЕКСТА ══════════
   Айфон считает надпись на кнопке обычным текстом страницы. Задержал палец чуть
   дольше обычного — и вместо нажатия получаешь лупу с «Скопировать / Выделить»:
   само нажатие при этом ПРОПАДАЕТ, а человек видит только, что кнопка не
   сработала. Это одна из причин жалобы «кнопки работают с непонятной задержкой».
   -webkit-touch-callout гасит и само всплывающее меню: на кнопке со значком оно
   вылезает даже там, где выделять нечего.

   ЗАПРЕТ СТАВИТСЯ ПО ПРИРОДЕ ЭЛЕМЕНТА, А НЕ СПИСКОМ ИМЁН. Всё, что нажимают
   (кнопка, ссылка, элемент с ролью кнопки, метка, раскрывашка), попадает сюда
   само — включая кнопки, которых ещё нет. Плитки, пилюли чатов, карточки папок и
   строки меню отдельно не перечислены НАРОЧНО: все они и так кнопки либо элементы
   с ролью кнопки. Три служебные области добавлены целиком — шапка, боковое меню и
   капсула ввода: там нет текста для копирования, одни подписи к органам
   управления, а промах пальцем по такой подписи стоит потерянного нажатия. */
button, a, [role="button"], label, summary,
.topbar, .km-root, .term-input-row {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
/* ПЕРЕПИСКУ ВЫДЕЛЯТЬ И КОПИРОВАТЬ МОЖНО — прямое требование владельца: из ленты
   берут куски ответов и код. Лента лежит вне перечисленных выше областей, то есть
   запрет её и так не задевает; правило стоит ЯВНО, чтобы следующая правка сверху
   («а давайте запретим выделение на всём экране») не отобрала копирование молча.
   Поля ввода — туда же: без выделения в них нельзя поправить набранное. */
.snapshot, .snapshot *,
input, textarea, [contenteditable] {
  -webkit-user-select: text;
  user-select: text;
  -webkit-touch-callout: default;
}
/* А кнопки ВНУТРИ ленты (озвучить, развернуть список действий, карточка файла) —
   снова без выделения: правило выше вернуло его всему, что лежит в ленте, вместе
   с ними. Здесь оно отбирается обратно — по той же природе элемента. */
.snapshot button, .snapshot a, .snapshot [role="button"], .snapshot summary {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

/* ══════════ ПОЛОСКА ПОД ЧАСАМИ ══════════
   Одна и та же полоска в двух видах, переключается настройкой «Содержимое под
   часами» (Настройки → Внешний вид, признак <html data-underclock>).

   ВЫКЛЮЧЕНО (по умолчанию) — непрозрачная заливка --bg ровно на высоту верхнего
   безопасного поля. Приложение выглядит В ТОЧНОСТИ как раньше: там, где система
   рисует часы, лежит сплошной тёмный фон. Это же честный запасной вид на случай,
   если система опять сломает режим «под часами».

   ВКЛЮЧЕНО — заливки нет, вместо неё РАЗМЫТИЕ ТОГО, ЧТО ПРОЕЗЖАЕТ ПОД ЧАСАМИ, с
   МАСКОЙ-ГРАДИЕНТОМ: маска гасит размытие книзу, поэтому у полоски нет нижней
   грани — она не читается как приклеенная плашка (ровно этим «как в Телеграме»
   отличается от «плашки сверху»).

   ПОЧЕМУ ЧИСЛА ИМЕННО ТАКИЕ (владелец: «есть полоса вверху, её быть не должно;
   стекло совсем прозрачное, прозрачнее кнопок»). Прежний набор давал именно
   плашку, и вот чем:
   — притемнение начиналось с .55 — на светлом куске ленты это серая заливка,
     которую видно как отдельную поверхность;
   — маска была НЕПРОЗРАЧНА ровно до низа поля и гасла всего за половину поля
     (34 точки). На этом коротком отрезке разом кончались и притемнение, и
     размытие — глаз читал ровную горизонтальную грань там, где полоска
     кончается.
   Замеры на белом худшем случае (полоса чистого белого проезжает под часами),
   уровень серого 0…255 сверху вниз:
     было  — 116 у верха, 210 у низа поля, 255 к 102-й точке: провал в 139
             уровней и самый резкий участок 2,2 уровня на точку у 72-й;
     стало — 189 у верха, 229 у низа поля, 255 к 128-й: провал 66 уровней,
             самый резкий участок 1,2 уровня на точку.
   То есть притемнение вдвое слабее, а гаснет вдвое длиннее — ступени нет нигде.
   СОВСЕМ УБРАТЬ ПРИТЕМНЕНИЕ НЕЛЬЗЯ: часы и батарея в этом режиме всегда светлые,
   и на светлом куске ленты они пропали бы. Замер на реальном содержимом (под
   часами едет строка светлого текста): без полоски самая светлая точка даёт
   контраст с белыми часами всего 2,7:1 — часы на буквах теряются; с новой
   полоской 13,5:1 (со старой было 17,3:1 — запас, который никому не нужен, а
   плашку видно). Требование для читаемости — не меньше 4,5:1.
   НАСЫЩЕННОСТЬ (saturate) УБРАНА: она подкрашивала то, что под полоской, и на
   «Перламутре» верх экрана заметно отличался цветом от ленты — ещё один признак
   плашки. Размытие убавлено с 18 до 12: его задача — размазать буквы под часами,
   а не сделать из ленты серую кашу.

   ВЫСОТА ЗАВЯЗАНА НА --safe-top: нет безопасного поля — нет и полоски (ноль
   высоты). Значит включённая настройка на телефоне, где система ещё держит
   непрозрачную полосу с часами, ничего не пририсовывает.

   Слой поверх всего (z-index 100 — выше окон, их затемнения и бокового меню):
   часы и батарея в этом режиме всегда светлые, и верх приложения обязан
   оставаться тёмным при любом фоне, оформлении и любом открытом окне.
   Касаний не ловит. */
.sysbar {
  position: fixed; top: 0; left: 0; right: 0; z-index: 100;
  height: var(--safe-top);
  pointer-events: none;
  background: var(--bg);
}
:root[data-underclock] .sysbar {
  height: calc(var(--safe-top) * 2);
  /* Притемнение сверху вниз — под светлыми пузырями и «Перламутром» часы обязаны
     оставаться читаемыми; размытие само по себе картинку ОСВЕТЛЯЕТ. Начинается с
     .26 (было .55) и гаснет ступенями по всей высоте, а не обрывается у низа поля. */
  background: linear-gradient(180deg,
    rgba(0, 0, 0, .26) 0,
    rgba(0, 0, 0, .24) 30%,
    rgba(0, 0, 0, .14) 58%,
    rgba(0, 0, 0, .05) 80%,
    rgba(0, 0, 0, 0) 100%);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  /* Маска гаснет С САМОГО ВЕРХА и понемногу — площадки «непрозрачно до сих пор»
     больше нет. Именно она и рисовала грань: на её конце разом кончались и
     притемнение, и размытие. Проценты, а не точки: высота полоски и так считается
     от поля, при любом поле пропорции сохраняются.
     ПОЧЕМУ НЕ ДЛИННЕЕ 2 ПОЛЕЙ: ниже поля начинаются островки шапки (кружок меню и
     капсула названия, их верх — поле плюс 6). Полоска лежит ПОВЕРХ них, и там, где
     маска ещё плотная, она размывает сами кнопки — они выглядят мутными. К верху
     островков маска уже меньше половины, к их середине — почти прозрачна. */
  -webkit-mask-image: linear-gradient(180deg, #000 0, rgba(0, 0, 0, .95) 28%,
    rgba(0, 0, 0, .74) 44%, rgba(0, 0, 0, .42) 60%, rgba(0, 0, 0, .15) 78%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 0, rgba(0, 0, 0, .95) 28%,
    rgba(0, 0, 0, .74) 44%, rgba(0, 0, 0, .42) 60%, rgba(0, 0, 0, .15) 78%, transparent 100%);
}



/* ── Обёртка основного экрана (шапка + экран) ──
   Существует ради выдвижного бокового меню: сдвиг, уменьшение и скругление
   применяются ЗДЕСЬ, а не на body и не на #screen. Почему так, а не иначе:
   — на #screen висит класс screen-chat и слушатели видимой области (chats.js);
   — обёртка — единственный узел, который можно двигать целиком, не трогая ни
     колонку чата внутри, ни слушателей.
   Преобразование внешней обёртки НЕ трогает раскладку внутри: она считается в
   прежних единицах, меняется только отрисовка. При закрытом меню transform снят
   ПОЛНОСТЬЮ (правило .km-on снимается) — никаких остаточных преобразований, а
   значит и вмещающий блок для position:fixed внутри обёртки в обычном состоянии
   не подменяется.
   ЗДЕСЬ ЖЕ, В ЧАТЕ, ОБЁРТКА САДИТСЯ НА ЭКРАН (position:fixed, а при открытой
   клавиатуре top/height по числам visualViewport через --vv-top/--vv-height) —
   раньше это висело на body и стоило приложению нижних 68 точек экрана. Правила и
   подробное объяснение — в разделе экрана чата ниже. Открытое меню и раскладка
   чата при этом не спорят: сдвиг и подрезка — отрисовка, посадка на экран —
   геометрия, разные свойства одного элемента.
   Своя подложка --bg обязательна: панель меню лежит НИЖЕ обёртки по слоям, и без
   непрозрачной подложки просвечивала бы сквозь прозрачный .screen. */
#app-shell {
  /* ОБЁРТКА САДИТСЯ НА ЭКРАН — ТЕПЕРЬ НА ВСЕХ ЭКРАНАХ, А НЕ ТОЛЬКО В ЧАТЕ.
     Растянута между верхом и низом (top:0; bottom:0), без единиц высоты: нижняя
     грань прибитого элемента и ЕСТЬ та граница, до которой телефон разрешает
     рисовать, — её не надо угадывать числом. Прежний вариант (обёртка в потоке,
     flex:1 внутри тела высотой в окно) отдавал ей ровно 844 — та самая мёртвая
     полоса внизу вне чата.
     Пара к запасу прокрутки у тела (см. body выше): запертой странице телефон
     отдаёт 844, и bottom:0 встаёт на 844, — работают только вдвоём.
     БЕЗОПАСНОСТЬ ПРАВКИ: не отдай телефон полную высоту, обёртка окажется прежних
     844, то есть худший исход — «как было», а не поломка.
     КЛАВИАТУРА: правило body.kb-open (раздел чата ниже, числа visualViewport)
     сильнее этого и продолжает работать ровно как работало — не тронуто. */
  position: fixed; top: 0; left: 0; right: 0; bottom: 0; height: auto;
  z-index: 2;
  display: flex; flex-direction: column;
  background: var(--bg);
  transition: transform var(--km-ms) var(--km-ease);
}
/* Меню открыто: основной экран уезжает вправо на ширину панели. УМЕНЬШЕНИЯ НЕТ.
   Почему убрано: масштаб считается от центра, поэтому величина зазора зависела от
   высоты обёртки, а сверху к зазору ещё добавлялась пустая полоса под системными
   часами — снизу такой полосы нет, и поля выходили разными. Теперь оба поля
   заданы ПРЯМО числами (--km-pad-top сверху, --km-pad снизу): clip-path срезает
   ровно их, ничего не «получается побочно».
   Почему clip-path, а не отступы/границы/высота: он режет ТОЛЬКО отрисовку и не
   меняет ни одного размера внутри обёртки — раскладка чата, высота ленты и
   поведение клавиатуры остаются ровно теми же. Сдвига вниз (--km-drop) больше
   нет: верхний срез по построению не заходит за верхний отступ шапки, поэтому
   подрезать круглую кнопку слева нечем и компенсировать нечего. */
body.km-on #app-shell {
  transform: translateX(var(--km-w));
  -webkit-clip-path: inset(var(--km-pad-top) 0px var(--km-pad) 0px round var(--km-r));
  clip-path: inset(var(--km-pad-top) 0px var(--km-pad) 0px round var(--km-r));
  animation: km-clip-in var(--km-ms) var(--km-ease);
}
/* Поля и скругление приезжают/уезжают ПЛАВНО вместе с ходом экрана. Сделано
   анимацией, а не переходом: у закрытого состояния clip-path нет вовсе (лишний
   отсечающий слой на всех экранах не нужен), а переход из «нет значения» не
   интерполируется — был бы рывок в первом кадре. */
@keyframes km-clip-in {
  from { -webkit-clip-path: inset(0px 0px 0px 0px round 0px);
                 clip-path: inset(0px 0px 0px 0px round 0px); }
}
@keyframes km-clip-out {
  from { -webkit-clip-path: inset(var(--km-pad-top) 0px var(--km-pad) 0px round var(--km-r));
                 clip-path: inset(var(--km-pad-top) 0px var(--km-pad) 0px round var(--km-r)); }
  to   { -webkit-clip-path: inset(0px 0px 0px 0px round 0px);
                 clip-path: inset(0px 0px 0px 0px round 0px); }
}
body.km-closing #app-shell { animation: km-clip-out var(--km-ms) var(--km-ease); }

/* Мягкая тень уехавшего экрана — вместо прежней явной линии по краю (box-shadow
   «0 0 0 1px var(--border)» убран: край читался слишком резко).
   Почему отдельным элементом: у обёртки при открытом меню стоит clip-path, а он
   срезает и собственную тень элемента — своей тени у обёртки быть не может.
   Подкладка ПОВТОРЯЕТ видимую форму уехавшего экрана (те же поля --km-pad-top
   сверху и --km-pad снизу, то же скругление) и лежит ПОД ним: сам экран непрозрачный и подкладку
   закрывает, наружу выходит только мягкое затенение — по левому краю и лёгким
   касанием в оба поля, сверху и снизу поровну. Никаких новых цветов: прозрачная
   чернота, как у остальных затенений проекта.
   ПРАВЫЕ УГЛЫ НЕ СКРУГЛЯЕМ. Уехавшая карточка уходит за правый край экрана — её
   правые углы физически за пределами видимого, и сама карточка идёт до края
   ровной гранью. Подкладка же кончается ровно на краю экрана: было бы у неё своё
   скругление справа — из-под карточки выглядывала бы вторая форма со своим
   загибом (ровно то, что видно на снимке в правом верхнем углу). Скругление
   только слева, там где у карточки настоящий угол. */
/* Нижнюю грань задаём ГРАНЬЮ bottom, а не высотой от --app-h. Раньше было
   наоборот, и по делу: нижняя грань прибитых элементов не доходила до низа
   экрана, подкладка кончалась бы выше карточки. Теперь у карточки (#app-shell в
   чате) нижняя грань ровно эта же — прибитая, — и обе формы кончаются в одной
   точке при любом решении телефона. Вне чата число то же, что было. */
.km-shade {
  position: fixed; z-index: 1; pointer-events: none;
  left: var(--km-w); right: 0; top: var(--km-pad-top); bottom: var(--km-pad);
  border-radius: var(--km-r) 0 0 var(--km-r);
  opacity: 0; transition: opacity var(--km-ms) var(--km-ease);
  box-shadow: -6px 0 20px 2px rgba(0, 0, 0, .45);
}
body.km-on .km-shade { opacity: 1; }

/* Затемнение уехавшего экрана + еле заметный контур по краю уехавшей карточки.
   Пока меню открыто, видимая полоска основного экрана справа приглушается —
   внимание уходит на меню; при закрытии приглушение снимается полностью.
   Почему отдельным элементом ПОВЕРХ обёртки, а не filter/opacity на самой обёртке:
   любой фильтр подменяет вмещающий блок для position:fixed внутри неё, а внутри
   живёт раскладка чата (fixed + числа visualViewport) — трогать её нельзя.
   Геометрия повторяет ВИДИМУЮ форму уехавшего экрана: слева ровно край карточки
   (--km-w), сверху и снизу те же поля, то же скругление, справа — ровная грань до
   края экрана (карточка уходит за него, своего правого угла в видимой части у неё
   нет; своё скругление справа выглядело бы «завёрнутым» углом). Касаний не ловит —
   тап по полоске по-прежнему закрывает меню (обработчик на обёртке в menu.js).
   Цвет заливки — прозрачная чернота, как у остальных затенений проекта.
   КОНТУР карточки — ровно тот же, что у линии-разделителя над «Настройками»:
   1px solid var(--panel) (см. .km-footer ниже). Прежний --border читался белесой
   заметной линией; --panel на фоне --bg — еле различимый шов, одинаковый в обоих
   местах. Границей, а не inset-тенью: тень обводит и правую грань, то есть
   рисовала бы лишнюю вертикальную нить по самому краю экрана. */
/* Нижняя грань — так же гранью bottom, а не высотой (см. пояснение у .km-shade). */
.km-dim {
  position: fixed; z-index: 3; pointer-events: none;
  left: var(--km-w); right: 0; top: var(--km-pad-top); bottom: var(--km-pad);
  border: 1px solid var(--panel); border-right: 0;
  border-radius: var(--km-r) 0 0 var(--km-r);
  /* Приглушение уехавшего экрана. Подбиралось по живой проверке владельцем:
     .42 — «всё равно яркое», .68 — «ещё чуть-чуть». Итог .8: полоска справа
     читается как тень экрана, а не как живое содержимое. */
  background: rgba(0, 0, 0, .8);
  opacity: 0; transition: opacity var(--km-ms) var(--km-ease);
}
body.km-on .km-dim { opacity: 1; }
/* Пока меню открыто (и пока экран едет обратно), содержимое основного экрана не
   ловит касания: любой тап по видимой полоске справа попадает в саму обёртку и
   закрывает меню (обработчик в menu.js). Заодно это гасит подсветку кнопок под
   пальцем и не даёт «отпусканию» пальца после закрывающего касания провалиться в
   живую кнопку под ним. */
body.km-on #app-shell > *,
body.km-closing #app-shell > * { pointer-events: none; }

/* ── Шапка ── */
/* Три колонки: [бургер] [название] [правая группа]. Боковые колонки — 1fr, то есть
   пока места хватает, они РАВНЫ и название стоит по ИСТИННОМУ центру шапки. Когда
   правой группе становится тесно (кнопки + кружок непрочитанных + бейдж разрешений
   на узком экране), колонки расходятся до своего минимума, а название непрерывно
   занимает оставшуюся полосу и обрезается многоточием. Никаких замеров в JS и
   классов-переключателей — отсюда нет ни мигания первого кадра, ни скачка названия
   при появлении счётчиков. */
.topbar {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  column-gap: 8px;
  padding: var(--safe-top) 16px 0;
  /* 56 = круглая кнопка 44 (как системная «назад» у Apple) + по 6 воздуха сверху
     и снизу. Ровно столько, сколько нужно кнопкам: прежние 52 держали кнопку 40 с
     тем же воздухом. Высота шапки нигде не зашита числом в коде — экран чата
     заполняет остаток флексом (см. .screen-chat), поэтому рост на 4px просто
     отдаёт ленте на 4px меньше и ничего не двигает. */
  height: calc(56px + var(--safe-top));
  background: var(--bg);
  border-bottom: none;   /* без линии-разделителя — шапка сливается с фоном */
  position: sticky;
  top: 0;
  z-index: 10;
  flex: 0 0 auto;   /* шапка держит свою высоту — колонка чата не сожмёт её */
}
/* min-width:0 + overflow:hidden обязательны: без них средняя колонка требует
   ширину по полному имени и выдавливает боковые. */
.topbar .title {
  grid-column: 2; min-width: 0;
  display: flex; align-items: center; justify-content: center;
  color: var(--bright); font-weight: 600; font-size: 1.15rem;
  white-space: nowrap; overflow: hidden;
}
.topbar .title.has-drop { cursor: pointer; }
/* Само имя окна — единственное, что сжимается: длинное обрезаем многоточием.
   min-width:0 обязателен, иначе flex-ребёнок не даёт себя сжать и текст вылезает. */
.title-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Галочка-шеврон — ВПЛОТНУЮ к названию (не уезжает к краю: flex:none + маленький
   отступ). Рисуется вектором (см. app.js): текстовый глиф «⌄» садился на базовую
   линию и висел у низа строки; вектор в inline-flex центрируется честно. */
.title-chev { color: var(--dim); margin-left: 5px; flex: none;
  display: inline-flex; align-items: center; line-height: 0; }

/* Круглая кнопка слева — первая колонка грида, прижата к левому краю. ОДНА и та
   же кнопка на всех экранах: на главном и в чате она бургер, в разделах —
   стрелка «к чатам». Меняется ТОЛЬКО значок внутри.
   Видимый габарит 44 — как у системной круглой кнопки «назад» в настройках
   айфона; тач-цель добирается до 48 прозрачным расширением ::after (вид не
   меняется). Обводка 2px ОСТАЁТСЯ на самом элементе и остаётся именно 2px — на
   ней сидит индикатор связи (.burger[data-link=…] и мигание), поэтому кнопка и
   обязана быть одна: индикатор работает и в разделах.
   Значки — вектор 24×24 со штрихом 2 и currentColor: у Apple при том же круге
   глиф крупнее и толще, отсюда «солидность». Цвет задаётся здесь (--bright), при
   нажатии переключается вместе с заливкой. */
.burger {
  position: relative; justify-self: start;
  flex: 0 0 auto; width: 44px; height: 44px; border-radius: 50%;
  background: var(--panel); border: 2px solid var(--border); color: var(--bright);
  display: flex; align-items: center; justify-content: center;
  padding: 0; cursor: pointer;
  transition: border-color .2s;
}
/* inset:-4px, а не -2px: у absolute-потомка вмещающий блок — ПОЛЕ (padding box),
   обводка 2px в него не входит. 40 (поле) + 4 + 4 = 48 — ровно проектная тач-цель. */
.burger::after { content: ''; position: absolute; inset: -4px; border-radius: 50%; }
.burger svg { display: block; }
/* Два значка (бургер и стрелка) лежат ДРУГ НА ДРУГЕ и переключаются
   прозрачностью. Почему не подмена разметки: при подмене кнопка на кадр теряет
   содержимое и значок «прыгает»; здесь габариты кнопки не меняются вовсе, а
   переход идёт плавно. Ход короткий (.18с) — это смена состояния, а не движение. */
.burger-ic {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  transition: opacity .18s var(--km-ease);
}
.burger[data-mode="menu"] .burger-ic-back { opacity: 0; }
.burger[data-mode="back"] .burger-ic-menu { opacity: 0; }
/* Ободок бургера = индикатор связи (перенесён с бывшей точки #link-dot).
   Зелёный-мята — на связи, красный — нет связи, тёплый янтарь — подключение. */
.burger[data-link="online"]     { border-color: var(--border); }
.burger[data-link="offline"]    { border-color: var(--red); }
.burger[data-link="connecting"] { border-color: #E0B36A; animation: burger-blink 1.2s ease-in-out infinite; }
@keyframes burger-blink { 0%, 100% { border-color: #E0B36A; } 50% { border-color: rgba(224,179,106,.22); } }
.topbar .back {
  display: none;   /* кнопку «назад» убрали: слева бургер, шаг назад — жестом-смахиванием */
  background: none; border: none; color: var(--orange);
  font-size: 1rem; padding: 8px 6px; cursor: pointer;
  text-align: right; white-space: nowrap;
}
.topbar .back[hidden] { display: none; }
/* ── Правая группа шапки ──
   Третья колонка грида, прижата к правому краю (16px даёт padding шапки, своих
   margin-right у детей нет — промежутки держит gap). Порядок в разметке:
   [бейдж разрешений] [кружок непрочитанных] [карандаш] [«⋯»]. Кнопки-действия
   стоят ПОСЛЕДНИМИ, поэтому их координаты одни и те же во всех состояниях —
   счётчики расширяют группу влево, а не сдвигают кнопки. */
.topbar-right {
  grid-column: 3; justify-self: end;
  display: flex; align-items: center; gap: 8px;
}
/* ── Капсула кнопок-действий ──
   Обе кнопки (карандаш «новый чат» и «⋯») обняты ОДНОЙ обводкой-капсулой: подложка
   --panel, обводка 2px --border, скругление 999px, высота 44 — РОВНО диаметр
   круглой кнопки слева, как и было задумано (обе выросли с 40 вместе).
   Правый край капсулы отбит от края экрана на 16px паддингом .topbar.
   Арифметика: поле каждой кнопки = 44 − 2 − 2 = 40, промежуток между полями 12
   (2 + 8 + 2 — прежнее расстояние между двумя отдельными кнопками с обводкой).
   Когда видна одна кнопка, капсула сжимается по содержимому: 40 + 2 + 2 = 44 в
   ширину при высоте 44 — ровный круг, двойник бургера. Пустая капсула гасится
   признаком hidden (app.js). */
.topbar-caps {
  flex: 0 0 auto; height: 44px; border-radius: 999px;
  background: var(--panel); border: 2px solid var(--border);
  display: flex; align-items: stretch; gap: 12px;
}
.topbar-caps[hidden] { display: none; }
/* Кнопка внутри капсулы: своей подложки и своей обводки НЕТ (иначе «круг в круге»).
   Габарит — поле 40×40: ширина фиксированная, высота по капсуле (stretch). */
.topbar-act {
  position: relative; flex: 0 0 auto;
  width: 40px; border-radius: 50%;
  background: none; border: none; color: var(--bright);
  display: flex; align-items: center; justify-content: center;
  padding: 0; cursor: pointer; -webkit-appearance: none;
}
/* ОТКЛИК НА НАЖАТИЕ ЗДЕСЬ БОЛЬШЕ НЕ ЗАДАЁТСЯ, И ЭТО НЕ ПОТЕРЯ. Именно эта кнопка
   стала ОБРАЗЦОМ для всего приложения (заливка круга акцентом, глиф чёрный), и
   теперь тот же отклик даёт общая сторожка (js/press.js) — здесь, у бургера, у
   микрофона и у строк меню одинаково. Форма круга внутри капсулы сохраняется
   сама: сторожка заливает элемент по его собственному скруглению (50%), поэтому
   загорается только нажатая кнопка, а не вся капсула. */
.topbar-act svg { display: block; }
/* Тач-цель 48×48 прозрачным расширением: поле 40 + 4 + 4 по обеим осям. По вертикали
   выходит за капсулу (44) — это и есть требуемые 48 по вертикали. По горизонтали
   расширения соседних кнопок НЕ пересекаются: между полями 12, каждое берёт по 4,
   между целями остаётся 4. */
.topbar-act::after { content: ''; position: absolute; inset: -4px; border-radius: 50%; }
.topbar-act[hidden] { display: none; }
.topbar .gear {
  background: none; border: none; color: var(--orange);
  font-size: 1.25rem; line-height: 1; padding: 6px 8px; cursor: pointer;
}
.topbar .gear[hidden] { display: none; }
/* Точку связи в шапке больше не показываем — её роль взял ободок бургера.
   Элемент остаётся в DOM (из его data-state читает меню), но визуально скрыт. */
.link-dot {
  display: none;
  width: 13px; height: 13px; border-radius: 50%;
  background: var(--dim); transition: background .2s;
  flex: 0 0 auto;
}
.link-dot[data-state="online"]  { background: var(--green); }
.link-dot[data-state="offline"] { background: var(--red); }
.link-dot[data-state="connecting"] { background: var(--dim); }

/* ── Экран ── */
.screen {
  flex: 1;
  /* min-height:0 — чтобы колонка обёртки могла сжать экран до своей высоты, а не
     распираться содержимым (иначе прокрутка внутри .screen не включается). */
  min-height: 0;
  padding: 16px;
  padding-bottom: calc(16px + env(safe-area-inset-bottom));
  overflow-y: auto;
  /* Докрутили экран до края — дальше НИЧЕГО не прокручивается. У документа теперь
     есть невидимый запас прокрутки (см. min-height тела), и без этой строки дотяг
     пальцем за конец списка уходил бы в него: список переставал бы пружинить, а
     «прокрутка в никуда» читается как подвисание. Та же строка стоит у ленты чата. */
  overscroll-behavior: contain;
}
/* Единый вид заголовков разделов — как .km-grouphead: приглушённые прописные
   с разрядкой (холодный «перламутровый» тон), без оранжевого выделения. */
.screen-title { color: var(--dim); font-size: .72rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .1em; margin: 2px 0 16px; }
.muted { color: var(--dim); }
.center { text-align: center; }
.spaced { margin-top: 40px; }

/* ── Плитки меню ── */
.tile {
  display: block; width: 100%; text-align: left;
  background: var(--panel); color: var(--bright);
  border: 1px solid var(--border); border-radius: 14px;
  padding: 18px; margin: 0 0 12px; font-size: 1.1rem;
  min-height: 58px; cursor: pointer;
  -webkit-appearance: none;
}
/* СВОЕЙ ЗАЛИВКИ ПРИ НАЖАТИИ ЗДЕСЬ НЕТ, и дописывать её сюда не надо. Отклик у
   плитки общий для всего приложения: она проседает под пальцем, а её форма
   целиком заливается акцентом, подпись при этом чернеет (сторожка js/press.js).
   Значка у плитки нет — заливать, кроме самой формы, нечего; таково правило
   владельца, см. шапку сторожки.
   Заодно ушла и вторая половина прежней пары: строке-показанию (.state-line)
   больше не от чего защищаться, она мигала оранжевым от старого правила. */
/* Строка-показание: тот же вид, что у кнопки-плитки, но это НЕ кнопка — она
   ничего не делает по нажатию. Поэтому у неё нет курсора-указателя. */
.state-line { cursor: default; }
.tile.danger { border-color: var(--orange); }
.tile.tile-on { border-color: var(--orange); color: var(--orange); }
.tile.tile-on .chev { color: var(--orange); }
.tile .chev { float: right; color: var(--dim); }
/* Бейдж непрочитанного на плитке (раздел меню / чат) — у правого края. */
.tile .tile-badge { float: right; margin: 0 0 0 8px; }
/* Плитка чата: имя в одну строку с многоточием — длинное заданное имя окна
   («Рассылки утренние…») не ломает просторную карточку; бейдж и шеврон у
   правого края. */
.tile-chat { display: flex; align-items: center; }
.tile-chat .tile-name { flex: 1; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.tile-chat .tile-badge, .tile-chat .chev { float: none; flex: none; margin-left: 8px; }
/* Заголовок папки в списке чатов: имя слева, сумма непрочитанного справа. */
.chat-folder {
  display: flex; align-items: center; gap: 10px;
  color: var(--dim); font-size: .82rem; text-transform: uppercase;
  letter-spacing: .04em; margin: 16px 4px 8px;
}
.chat-folder .badge { margin: 0; }

/* ══════════ ГЛАВНЫЙ ЭКРАН (лендинг) ══════════
   Сверху вниз: карточка лимитов (два кольца + строка подписки) → раздел «Папки» с
   лентой карточек (горизонтальная прокрутка) → раздел чатов набором пилюль.
   Гамма «Ночной перламутр»: только переменные из :root, холодные тонкие линии,
   приглушённые прописные заголовки с разрядкой. Карточки «Сейчас в работе» и
   чипов-фильтров больше нет — состояние окна видно точкой у его пилюли. */
.landing { display: flex; flex-direction: column; min-height: 100%; }

/* ── Заголовок раздела: прописные + тонкая линия во всю остальную ширину
      (+ необязательная кнопка справа, например «+ папка»). ── */
.lz-sec { display: flex; align-items: center; gap: 10px; margin: 24px 0 13px; }
.lz-sec:first-child { margin-top: 2px; }
.lz-sec-t {
  flex: 0 1 auto; min-width: 0; color: var(--dim);
  font-size: .8rem; font-weight: 600; text-transform: uppercase; letter-spacing: .1em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Линия — гибкая середина: занимает всё, что осталось между заголовком и кнопкой. */
.lz-sec-line { flex: 1 1 auto; min-width: 12px; height: 1px; background: var(--border); }
/* Контурная пилюля-кнопка раздела («+ папка»): текст акцентом, тач-цель 44 по
   вертикали добирается прозрачным расширением — вид остаётся компактным. */
.lz-addfold {
  position: relative; flex: 0 0 auto;
  background: none; border: 1px solid var(--border); color: var(--orange);
  border-radius: 999px; padding: 8px 16px; font-size: .88rem; font-weight: 600;
  font-family: inherit; cursor: pointer; -webkit-appearance: none; white-space: nowrap;
}
.lz-addfold::after { content: ''; position: absolute; left: 0; right: 0; top: -8px; bottom: -8px; }
/* Своей заливки при нажатии нет: отклик даёт сторожка — см. .tile. */

/* ── Карточка лимитов ── */
/* Паддинг по бокам скромный (14): половинки карточки узкие, и каждый лишний
   пиксель идёт подписи «обнулится через 42 минуты» — иначе она рвётся на три
   строки вместо двух. */
.lz-lim {
  background: var(--panel); border: 1px solid var(--border); border-radius: 14px;
  padding: 16px 14px 14px; margin: 2px 0 4px;
}
/* Карточка нажимается ЦЕЛИКОМ и ведёт в раздел «Лимиты и расход» — цель размером
   с карточку, попадать пальцем не надо. «подробнее ›» внутри осталось надписью-
   подсказкой (кнопки внутри кнопки нет). */
.lz-lim[role="button"] { cursor: pointer; }
/* Своей заливки при нажатии нет: отклик даёт сторожка — см. .tile. */
/* Заголовок внутри карточки: «ЛИМИТЫ» слева, ссылка «подробнее ›» справа. */
.lz-cardhead { display: flex; align-items: center; gap: 10px; margin-bottom: 13px; }
.lz-cardhead .lz-sec-t { flex: 1 1 auto; }
.lz-morelink {
  position: relative; flex: 0 0 auto;
  background: none; border: none; color: var(--orange);
  font-size: .88rem; font-family: inherit; padding: 0; cursor: pointer;
  -webkit-appearance: none; white-space: nowrap;
}
.lz-morelink::after { content: ''; position: absolute; left: -10px; right: -10px; top: -14px; bottom: -14px; }
/* Два кольца рядом, между ними тонкая вертикальная линия. */
.lz-rings { display: flex; align-items: center; }
.lz-ring-sep { flex: 0 0 auto; width: 1px; align-self: stretch; min-height: 58px;
  background: var(--border); margin: 0 11px; }
.lz-ring-cell { flex: 1 1 0; min-width: 0; display: flex; align-items: center; gap: 9px; }
/* Кольцо: вектор + процент отдельным узлом по центру (масштабируется со шрифтом).
   Размер холста в разметке 58 (chats.js), здесь он и остаётся — вектор тянется по
   viewBox, поэтому на узком экране кольцо просто сжимается и остаётся ровным. */
.lz-ring { position: relative; flex: 0 0 auto; width: 58px; height: 58px;
  display: flex; align-items: center; justify-content: center; }
.lz-ring svg { display: block; width: 58px; height: 58px; }
/* Цвета кольца — здесь, а не в атрибутах вектора: незаполненная часть --border,
   заполненная (дуга по проценту) --orange. Разной насыщенности нет. */
.lz-ring-track { fill: none; stroke: var(--border); }
.lz-ring-arc   { fill: none; stroke: var(--orange); }
.lz-ring-pct {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  color: var(--bright); font-size: 1.02rem; font-weight: 700;
  font-variant-numeric: tabular-nums; letter-spacing: -.02em;
}
.lz-ring-pct.none { color: var(--dim); font-weight: 600; }
.lz-ring-meta { flex: 1 1 auto; min-width: 0; }
.lz-ring-name { color: var(--text); font-size: .94rem; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lz-ring-sub { color: var(--dim); font-size: .76rem; line-height: 1.35; margin-top: 2px;
  overflow-wrap: anywhere; }
/* Строка подписки — за тонкой горизонтальной линией внизу карточки. */
.lz-plan {
  display: flex; align-items: baseline; gap: 10px;
  border-top: 1px solid var(--border); margin-top: 13px; padding-top: 11px;
}
.lz-plan[hidden] { display: none; }
/* Текст подписки переносится (не обрезается многоточием): «Подписка Max 20x ·
   оплачена до 14 августа» на 390 не влезает в одну строку рядом с остатком дней,
   а обрезать её на «14 авгус…» — терять смысл. */
.lz-plan-t { flex: 1 1 auto; min-width: 0; color: var(--text); font-size: .84rem;
  line-height: 1.35; overflow-wrap: anywhere; }
.lz-plan-left { flex: 0 0 auto; color: var(--dim); font-size: .84rem;
  font-variant-numeric: tabular-nums; }

/* ── Блок почты: конверт с темой последнего письма ──
   Стоит ПОД карточкой лимитов и НАД заголовком «ПРОЕКТЫ» — решение владельца от
   27 августа 2026, выбранное им по превью из четырёх видов. Разбор самого
   решения — в js/screens/home.js, здесь только вид.
   МАТЕРИАЛ НЕ СВОЙ, А СОСЕДСКИЙ: та же плоская подложка одной плотностью
   (.085) и тот же волосок --glass-edge, что у плиток проектов и пилюль чатов
   (см. раздел «ПЛИТКИ ПРОЕКТОВ И ПИЛЮЛИ ЧАТОВ — ПЛОСКИЕ»). Ни осветления, ни
   тени: блок обязан лежать в одной плоскости с соседями, а не всплывать над
   ними. Числа перенесены из превью дословно.
   Своей заливки при нажатии нет: отклик даёт сторожка — см. .tile. */
.pm {
  margin: 10px 0 2px; cursor: pointer; -webkit-appearance: none;
  font-family: inherit;
  padding: 11px 16px 12px; border-radius: 18px;
  background: rgba(255, 255, 255, .085); border: 1px solid var(--glass-edge);
}
/* Конверт и цифра — одной строкой; цифра прижата к правому краю. Высота строки
   задана снизу (30), а не ростом значка: без неё блок без темы схлопывался бы
   ниже, чем блок с темой, и полоса дёргалась бы по высоте на приход письма. */
.pm-line { display: flex; align-items: center; min-height: 30px; }
.pm-ic { flex: 0 0 auto; display: flex; align-items: center; color: var(--text); }
.pm-n { flex: 0 0 auto; margin-left: auto; color: var(--bright); font-weight: 700;
  font-size: 1.02rem; font-variant-numeric: tabular-nums; letter-spacing: -.02em; }
/* Тема — мелко, приглушённо и В ОДНУ СТРОКУ с многоточием: длинная тема,
   переносясь на вторую строку, растила бы блок и двигала весь экран вниз. */
.pm-subj { color: var(--dim); font-size: .78rem; line-height: 1.3; margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* ПУСТО — БЛОК ПРИГЛУШЁН, А НЕ СПРЯТАН: подложка бледнее, конверт и цифра
   приглушённым тоном. Исчезающая дверь в почту заставляла бы искать её заново. */
.pm-off { background: rgba(255, 255, 255, .045); }
.pm-off .pm-ic { color: var(--dim); }
.pm-off .pm-n { color: var(--dim); font-weight: 600; }

/* ── Лента карточек папок: листается пальцем по горизонтали ──
   Карточки КВАДРАТНЫЕ; в экран встают три, и виден край четвёртой — сразу
   понятно, что ленту можно листать. Полосу прокрутки не показываем.

   Про жесты (владелец жаловался, что лента «плохо срабатывает»):
   1) touch-action было pan-x — это ЗАПРЕЩАЛО вертикальное панорамирование в
      месте ленты. Палец, ведённый по ленте вниз, не прокручивал экран вообще, а
      чуть скошенный жест уходил в никуда. Стало pan-x pan-y: направление
      выбирает браузер — горизонталь листает ленту, вертикаль прокручивает экран.
   2) overscroll-behavior-x: contain — доехав до края, лента не «пробрасывает»
      жест наружу: ни родителю, ни системному «назад» от левого края экрана.
   3) scroll-snap-type сменён с proximity на mandatory — карточки одной ширины,
      поэтому лента всегда встаёт ровно на начало карточки, а не «где придётся».
   Жест «назад» внутри ленты не перехватывается — .lz-strip уже в исключениях
   swipeBack (js/app.js). */
.lz-strip {
  /* Зазор между карточками вынесен в переменную НЕ ради красоты: из него
     вычитается ширина концевой распорки (::after ниже). Иначе воздух в конце
     ленты = распорка + зазор, то есть на целый зазор больше, чем поле первой
     карточки, — лента выглядела прижатой влево (замер: слева 16, справа 26). */
  --strip-gap: 10px;
  display: flex; gap: var(--strip-gap); overflow-x: auto; overflow-y: hidden;
  /* flex-start, а не растягивание: иначе карточки растянулись бы по высоте самой
     высокой в ленте, и заданная им пропорция «квадрат» перестала бы работать. */
  align-items: flex-start;
  touch-action: pan-x pan-y; overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: x mandatory;
  /* Прилипание считается от НАЧАЛА видимой области ленты, а лента теперь во всю
     ширину экрана. Без этой поправки лента сама подматывалась на 16px и первая
     карточка вставала вплотную к краю экрана (поле у неё есть, но прилипание его
     «съедало»). Со scroll-padding карточки прилипают к той же линии, где начинается
     остальное содержимое экрана. */
  scroll-padding-left: 16px;
  scrollbar-width: none;
  /* Лента идёт ВО ВСЮ ШИРИНУ экрана (отрицательные поля гасят отступы .screen) —
     это те самые 32px, за счёт которых карточки стали крупнее.
     Отступ слева даёт НЕ padding ленты, а поле первой карточки, а воздух справа —
     распорка ::after. Так надёжно на любом движке: боковой padding у
     горизонтального flex-контейнера с прокруткой браузеры считают по-разному
     (именно из-за этого «во всю ширину» раньше отказались — первая карточка
     прилипала к краю экрана), а поле и распорка — обычные элементы потока. */
  margin: 0 -16px; padding: 2px 0 4px;
}
.lz-strip::-webkit-scrollbar { width: 0; height: 0; }
.lz-strip > :first-child { margin-left: 16px; }
/* Распорка в конце ленты: её собственная ширина плюс зазор перед ней должны дать
   ровно те же 16, что и поле первой карточки слева. */
.lz-strip::after { content: ''; flex: 0 0 calc(16px - var(--strip-gap)); align-self: stretch; }
/* Карточка папки — КВАДРАТ.
   Ширина: проценты у flex-basis считаются от ВИДИМОЙ ширины ленты (не от всей
   прокручиваемой), а лента теперь во всю ширину экрана. Отсюда формула:
   54px = 16 (поле первой карточки) + ТРИ зазора по 10 (третий — перед четвёртой
   карточкой, его легко забыть) + 8 на видимый «край следующей».
   Проверка на 390: 16 + 112 + 10 + 112 + 10 + 112 = 372, ещё зазор 10 — и от 382
   до 390 виден край четвёртой. Ровно три в экране плюс край, как и было, но сама
   карточка стала крупнее (103 → 112).
   Высота: aspect-ratio, а не min-height, — сторона равна ширине на любом экране,
   так что форма остаётся квадратной и на 320, и на 430.
   min-width: 0 обязателен: у флекс-элемента ширина по умолчанию не опускается
   ниже самого длинного слова, и одно длинное имя папки распирало бы квадрат. */
.lz-fold {
  flex: 0 0 calc((100% - 54px) / 3); min-width: 0;
  /* «⋯» действий проекта прибито к углу квадрата — отсюда position: relative. */
  position: relative;
  aspect-ratio: 1 / 1; align-self: flex-start;
  scroll-snap-align: start;
  display: flex; flex-direction: column; align-items: flex-start;
  overflow: hidden;                      /* страховка: ничего не вылезает за квадрат */
  background: var(--panel); border: 1px solid var(--border); border-radius: 14px;
  padding: 12px 13px; text-align: left; cursor: pointer;
  -webkit-appearance: none; font-family: inherit;
}
/* Своей заливки при нажатии нет: отклик даёт сторожка — см. .tile.
   Подложка ниже — это ВЫБРАННАЯ папка, состояние, а не нажатие: она остаётся. */
/* Выбранная папка выделена: акцентная обводка, мягкая акцентная подложка. */
.lz-fold.sel { border-color: var(--orange); background: var(--accent-soft); }
/* Звезда «избранное» — ЛЕВЫЙ ВЕРХНИЙ УГОЛ квадрата, ровно там, где до 27 августа
   2026 стоял цветной квадратик-метка. Метка убрана по слову владельца: «перенеси
   звёздочку вместо квадратиков в левом верхнем углу, они на хуя нужны, это как
   раз для избранного хорошее место». Гамма метки ушла вместе с ней.

   СТОИТ В ПОТОКЕ, а не слоем поверх, как «⋯»: метка занимала высоту (13 + 7
   отступа = 20px), и от неё отсчитывалось имя проекта. Слой поверх высоты не
   держит — имя уехало бы вверх на всех карточках разом.
   Площадка под палец 26px, а поток обязан остаться прежним: отрицательные поля
   гасят разницу (26 - 6 = 20), и значок 17px встаёт центром туда же, где был
   центр метки. Меняешь размер — пересчитай поля, иначе поедет имя.
   БЕЗ НАДПИСЕЙ: состояние читается цветом, как везде в этом приложении. */
.lz-fold-star {
  flex: 0 0 auto; width: 26px; height: 26px; margin: -6px 0 0 -4px;
  border: none; background: none; color: var(--dim); opacity: .5; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  padding: 0; -webkit-appearance: none;
}
.lz-fold-star svg { width: 17px; height: 17px; }
/* Отмеченный проект: тот же контур, но акцентом и залитый — звезда становится
   заметной пятном, а не только цветом. Зелёного и красного здесь быть не может:
   они в приложении значащие («есть непрочитанное», «ошибка»). */
.lz-fold-star.on { color: var(--orange); opacity: 1; }
.lz-fold-star.on svg path { fill: currentColor; }
/* У «Без проекта» звезды нет (отмечать там нечего) — но её высоту карточка
   держит всё равно, иначе имя этой карточки встало бы выше имён соседних. */
.lz-fold-name:first-child { margin-top: 20px; }
/* Имя папки: до ДВУХ строк с многоточием. В квадрате ширины меньше, чем в бывшем
   широком прямоугольнике, поэтому обрезка в одну строку съедала бы половину
   названий; вторая строка возвращает смысл. Многоточие рисует сам line-clamp. */
.lz-fold-name {
  width: 100%; min-width: 0;
  color: var(--bright); font-size: 1.02rem; font-weight: 600;
  letter-spacing: -.01em; line-height: 1.25;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden; overflow-wrap: anywhere;
}
/* Подпись «сколько новых · сколько чатов» — мелкая, приглушённая, прижата к низу
   квадрата (margin-top: auto), чтобы имя в одну и в две строки не двигало её. */
.lz-fold-sub {
  width: 100%; min-width: 0; margin-top: auto;
  color: var(--dim); font-size: .78rem; line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* «⋯» действий проекта — верхний правый угол квадрата. Тот же значок и тот же
   приём, что у «⋯» в строке панели разговоров (.cp-more), но меньше: там строка
   во всю ширину экрана, здесь квадрат в треть ширины, и круг в 44 съел бы
   половину карточки. Звезда слева и имя ниже — под него не попадают.
   Верхний слой обязателен: карточка — сама нажимаемая. */
.lz-fold-more {
  position: absolute; top: 0; right: 0; z-index: 1; width: 30px; height: 30px;
  border: none; background: none; color: var(--dim); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  padding: 0; -webkit-appearance: none;
}
.lz-fold-more svg { width: 18px; height: 18px; }

/* ── Чаты пилюлями: переносятся по строкам ── */
.lz-pills { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 8px; }
.lz-pill {
  display: inline-flex; align-items: center; gap: 9px; max-width: 100%;
  min-height: 48px; padding: 10px 17px; border-radius: 999px;
  background: var(--panel); border: 1px solid var(--border); color: var(--bright);
  font-size: 1.02rem; font-family: inherit; cursor: pointer; -webkit-appearance: none;
}
/* Своей заливки при нажатии нет: отклик даёт сторожка — см. .tile. */
.lz-pill-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lz-pill-n { flex: 0 0 auto; color: var(--bright); font-weight: 700; font-size: .91rem;
  font-variant-numeric: tabular-nums; }
/* «+ чат» — пунктирная обводка, текст акцентом (кнопка-заготовка, не чат). */
.lz-pill-add {
  background: none; border: 1px dashed var(--orange); color: var(--orange); font-weight: 600;
}
/* Нет ни одного открытого чата — приглушённая строка над пилюлей «+ чат». */
.lz-none { color: var(--dim); font-size: .97rem; padding: 4px 2px 12px; }

/* Узкий экран (320): кольца и подписи чуть мельче, чтобы половинки карточки
   лимитов не сжимались до переноса каждого слова. */
@media (max-width: 359px) {
  /* Половинки карточки лимитов тут совсем узкие: подпись «обнулится через 42
     минуты» и раньше ломалась на три строки. Кольцо и проценты крупнее, а вот
     саму подпись почти не растим и поля карточки оставляем прежними — иначе
     строк становится четыре. */
  .lz-lim { padding: 14px 14px 12px; }
  .lz-ring { width: 53px; height: 53px; }
  .lz-ring svg { width: 53px; height: 53px; }
  .lz-ring-pct { font-size: .89rem; }
  .lz-ring-cell { gap: 7px; }
  .lz-ring-sep { margin: 0 8px; min-height: 53px; }
  .lz-ring-name { font-size: .88rem; }
  .lz-ring-sub { font-size: .66rem; }
  .lz-plan-t, .lz-plan-left { font-size: .82rem; }
  /* Карточки папок: на 320 сторона квадрата ~91px, и крупные кегли в неё уже не
     помещаются. Зазор 8 вместо 10 (48px = 16 поля первой + 3×8 + 8 на край
     следующей), поля и подписи мельче — три карточки и край четвёртой остаются
     на месте, а содержимое не наезжает и не обрезается. */
  .lz-strip { --strip-gap: 8px; }
  .lz-fold { flex-basis: calc((100% - 48px) / 3); padding: 9px 10px; }
  /* Место звезды в потоке — те же 16px, что раньше держала метка (11 + 5). */
  .lz-fold-star { margin: -7px 0 -3px -4px; }
  .lz-fold-name:first-child { margin-top: 16px; }
  .lz-fold-name { font-size: .89rem; line-height: 1.2; }
  .lz-fold-sub { font-size: .71rem; line-height: 1.25; }
}

/* Список чатов: чистые строки, тонкие холодные разделители, без тяжёлых рамок.
   Остался в деле у панели открытых чатов и у результатов поиска. */
.lz-list { display: flex; flex-direction: column; }
.lz-row {
  display: flex; align-items: center; gap: 12px;
  min-height: 60px; padding: 12px 2px; cursor: pointer;
}
.lz-row + .lz-row { border-top: 1px solid var(--border); }
.lz-row-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.lz-row-name {
  color: var(--bright); font-size: 1rem; font-weight: 600; letter-spacing: -.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lz-row-sub {
  color: var(--dim); font-size: .82rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lz-ind { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; }
.lz-ind-num { color: var(--dim); font-size: .9rem; font-variant-numeric: tabular-nums; }
.lz-ind-num.unread { color: var(--bright); font-weight: 600; }
.lz-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
.lz-dot.green { background: var(--green); }
.lz-dot.blue  { background: var(--orange); }


/* ── Живой поиск по всем чатам ──
   Поля поиска на ГЛАВНОМ экране больше нет (решение Дмитрия): блок стоит в
   бургер-меню, видимый сразу, без нажатий. Вид — КРУПНАЯ ОВАЛЬНАЯ КАПСУЛА
   (скругление максимальное), лупа внутри слева, во всю ширину; высота с запасом
   от 44 пунктов. Стили общие и не привязаны к лендингу (.srch-*). Палитра — та же
   «Ночной перламутр»: подложка --panel, обводка --border, акцент --orange. */
.srch-field {
  display: flex; align-items: center; gap: 10px; min-height: 54px;
  background: var(--panel); border: 1px solid var(--border);
  border-radius: 999px; padding: 0 6px 0 18px;
}
.srch-field:focus-within { border-color: var(--orange); }
.srch-ic { display: flex; color: var(--dim); flex: 0 0 auto; }
.srch-input {
  flex: 1 1 auto; min-width: 0; background: none; border: none; outline: none;
  color: var(--bright); font-size: 1.02rem; font-family: inherit;
  padding: 15px 0; -webkit-appearance: none;
}
.srch-input::placeholder { color: var(--dim); }
/* Крестик «очистить» — полноценная тач-цель 44×44 (палец, не курсор). */
.srch-clear {
  flex: 0 0 auto; width: 44px; height: 44px; border: none; background: none;
  color: var(--dim); font-size: 1rem; line-height: 1; cursor: pointer;
  -webkit-appearance: none; font-family: inherit; border-radius: 50%;
}
.srch-clear[hidden] { display: none; }
.srch-status { color: var(--dim); font-size: .9rem; padding: 8px 2px 12px; }
.srch-status[hidden] { display: none; }
.srch-results[hidden] { display: none; }
/* Строка-фрагмент под именем чата в результатах поиска (контентное совпадение). */
.lz-row-snip {
  color: var(--dim); font-size: .82rem; margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── Панель всех открытых чатов (нажатие на название в шапке) ──
   Живёт в общей модальной подложке (.modal-back), поэтому вид единый с окнами
   подтверждения. От обычной .modal отличается только тем, что тянется по высоте и
   прокручивается внутри себя, а текст в строках — по левому краю.
   Высоту ограничиваем видимым окном минус безопасные зоны — панель не вылезает ни
   под «бровь», ни под домашний индикатор iPhone. */
/* Селектор с двумя классами — чтобы перебить .modal (она ниже в файле и задаёт
   центрирование текста, свои паддинги и max-width). */
/* Столбик и предел высоты приходят из общего правила .modal.mw — здесь только
   то, чем панель чатов отличается: она шире, поля теснее, текст по левому краю. */
.modal.cp-pop {
  padding: 10px 14px 14px; max-width: 420px; text-align: left;
}
/* Тело этой панели само НЕ прокручивается: внутри у неё поле поиска (стоит на
   месте) и список (.cp-body, вот он и едет). Своя прокрутка на теле дала бы две
   вложенные полосы прокрутки — палец попадал бы то в одну, то в другую. */
.cp-pop .mw-body {
  display: flex; flex-direction: column; min-height: 0;
  overflow: visible; margin: 0; padding: 0;
}
.cp-pop .srch-field { flex: 0 0 auto; min-height: 46px; margin: 8px 0 4px; padding: 0 4px 0 16px; }
.cp-pop .srch-input { font-size: 1rem; padding: 12px 0; }
/* Прокручиваемая середина: список окон (или результаты поиска). */
.cp-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; margin: 0 -4px; padding: 0 4px; }
.cp-body::-webkit-scrollbar { width: 0; }
.cp-list[hidden] { display: none; }
/* Заголовок группы-папки — как заголовки разделов меню. */
.cp-group {
  color: var(--dim); font-size: .7rem; font-weight: 600; letter-spacing: .12em;
  text-transform: uppercase; margin: 14px 2px 4px;
}
.cp-group:first-child { margin-top: 6px; }
/* Строка окна: тот же вид, что в списке чатов (.lz-row), плюс «⋯» справа. */
.cp-row { gap: 8px; }
.cp-row + .cp-row { border-top: 1px solid var(--border); }
.cp-group + .cp-row { border-top: none; }
/* Текущий открытый чат явно выделен: перивинклевая подложка и полоска слева. */
.cp-cur {
  background: var(--accent-soft); border-radius: 12px;
  box-shadow: inset 3px 0 0 var(--orange); padding-left: 10px;
}
.cp-cur + .cp-row, .cp-row + .cp-cur { border-top: none; }
.cp-more {
  flex: 0 0 auto; width: 44px; height: 44px; border-radius: 50%;
  border: none; background: none; color: var(--dim); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  padding: 0; -webkit-appearance: none;
}
.cp-empty { color: var(--dim); text-align: center; padding: 28px 12px; }
/* Пока грузим окна, в панели стоят заготовки строк (js/ui.js → skChatRows) —
   ровно той же формы, что настоящие строки: подстановка списка не двигает панель. */

/* ── Подсветка совпадений (общая для лендинга и поиска в диалоге) ── */
.hl { background: var(--accent-soft); color: var(--bright); border-radius: 3px; }
.hl-active {
  background: #F5D90A33; color: var(--bright);
  outline: 1px solid var(--orange); border-radius: 3px;
}

/* ── Поиск внутри диалога: строка, которая выезжает из-под шапки ──
   Открывается пунктом «Найти в чате» в меню действий чата. Анимируем ОБЁРТКУ .find-wrap
   (высота + прозрачность), а сама полоса внутри не меняется — иначе прыгали бы
   её рамка и отступы. Пока свёрнута: нулевая высота, не ловит касания. */
.find-wrap {
  overflow: hidden; max-height: 0; opacity: 0; margin-bottom: 0;
  pointer-events: none;
  transition: max-height .24s cubic-bezier(.22,.61,.36,1),
              opacity .18s ease, margin-bottom .24s cubic-bezier(.22,.61,.36,1);
}
.find-wrap.open { max-height: 84px; opacity: 1; margin-bottom: 8px; pointer-events: auto; }
.find-bar {
  display: flex; align-items: center; gap: 2px;
  background: var(--panel); border: 1px solid var(--border);
  border-radius: 14px; padding: 0 4px 0 12px; margin: 0;
}
.find-ic { display: flex; color: var(--dim); flex: 0 0 auto; }
.find-input {
  flex: 1 1 auto; min-width: 0; background: none; border: none; outline: none;
  color: var(--bright); font-size: 1rem; font-family: inherit;
  padding: 12px 8px 12px 0; -webkit-appearance: none;
}
.find-input::placeholder { color: var(--dim); }
/* Счётчик «3 из 12» — место под самый длинный вид, чтобы стрелки не дёргались. */
.find-count {
  color: var(--dim); font-size: .82rem; flex: 0 0 auto;
  font-variant-numeric: tabular-nums; min-width: 58px; text-align: center;
}
/* Стрелки и крестик — тач-цели 44×44 (требование к пальцу на iPhone). */
.find-nav, .find-close {
  background: none; border: none; color: var(--text); cursor: pointer;
  line-height: 1; padding: 0; -webkit-appearance: none;
  flex: 0 0 auto; font-family: inherit; border-radius: 50%;
  width: 44px; height: 44px; display: flex; align-items: center; justify-content: center;
}
.find-nav { color: var(--text); }
.find-nav svg { display: block; }
.find-close { color: var(--dim); font-size: 1.05rem; }

/* ── Индикатор жеста «потянуть вниз — обновить» ── */
.ptr {
  position: fixed; top: -40px; left: 50%;
  transform: translateX(-50%);
  width: 34px; height: 34px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--panel); color: var(--dim);
  border: 1px solid var(--border);
  font-size: 1.2rem; line-height: 1; opacity: 0; z-index: 60;
  transition: opacity .15s ease;
  pointer-events: none;
}
.ptr.ready { color: var(--orange); border-color: var(--orange); }
.ptr.spin { animation: ptr-spin .6s linear infinite; }
@keyframes ptr-spin { from { rotate: 0deg; } to { rotate: 360deg; } }

/* ── Контекст окна над снимком ── */
.snap-ctx { color: var(--dim); font-size: 0.95rem; margin: -2px 0 10px; }

/* ── Кнопки ── */
/* Кнопкой бывает и ССЫЛКА: «Скачать» и «Открыть» у карточки файла — настоящие
   ссылки, потому что и сохранение, и показ документа умеет сам браузер
   (js/filecard.js). Две строки ниже уравнивают ссылку с кнопкой: без них ссылка
   приходит с подчёркиванием и с надписью, прижатой влево, — и ряд кнопок на
   карточке переставал совпадать со старым приложением, где обе были кнопками. */
.btn {
  display: inline-block; border: none; border-radius: 12px;
  padding: 16px 22px; font-size: 1.05rem; cursor: pointer;
  min-height: 52px; min-width: 110px;
  text-decoration: none; text-align: center;
}
.btn-primary { background: var(--orange); color: #000; font-weight: 600; }
.btn-ghost { background: var(--panel); color: var(--bright); border: 1px solid var(--border); }
.btn-row { display: flex; gap: 12px; margin-top: 20px; }
.btn-row .btn { flex: 1; }

/* ── Поле ввода кода ── */
.pin-wrap { max-width: 320px; margin: 48px auto 0; text-align: center; }
.pin-input {
  width: 100%; font-size: 1rem; text-align: center;
  font-family: ui-monospace, Menlo, monospace; letter-spacing: .02em;
  background: var(--panel); color: var(--bright);
  border: 1px solid var(--border); border-radius: 14px;
  padding: 16px; margin: 18px 0; -webkit-appearance: none;
}
.pin-input:focus { outline: none; border-color: var(--orange); }
.pin-msg { color: var(--red); min-height: 1.4em; margin-bottom: 10px; }

/* ── ВСПЛЫВАЮЩЕЕ ОКНО: ОДИН ОБРАЗЕЦ НА ВСЁ ПРИЛОЖЕНИЕ ──
   Подтверждения, меню действий, панель открытых чатов, поиск, переименование,
   просмотр файла, список непрочитанного — всё это ОДНА и та же пара
   .modal-back (затемнение) + .modal (окно). Собирается в одном месте кода
   (js/ui.js → openWindow), поэтому затемнение, скругление, кромка, тень,
   пружина появления и крестик у всех окон одинаковые по определению, а не
   «приведены к одному виду руками». */
/* Затемнение на ВЕСЬ экран — ГРАНЬЮ bottom:0, а не высотой от --app-h. Раньше
   стояла высота: нижняя грань прибитых элементов до низа экрана не доставала, и
   снизу оставалась незатемнённая полоса. Теперь низ экрана прибитым доступен (см.
   раздел про раскладку чата), и грань — самый честный способ: она совпадёт с
   низом при любом решении телефона, а число --app-h могло с ним разойтись. */
/* ВИД, РАЗМЕРЫ И ДВИЖЕНИЕ ОКНА ЖИВУТ ОДНИМ КУСКОМ В КОНЦЕ ФАЙЛА — раздел «ОКНО
   ПО ЭТАЛОНУ ВЛАДЕЛЬЦА». Здесь остались только те правила общей части, которые не
   про сам образец: подложка-ловец нажатий и мелочи содержимого окон. */
/* Подложка — ПРОЗРАЧНЫЙ ЛОВЕЦ НАЖАТИЙ на весь экран. Само затемнение лежит
   отдельным узлом внутри неё (.mw-scrim): у затемнения и у окна в эталоне разные
   ходы прозрачности, и будь окно внутри затемнения, оно гасло бы вместе с ним. */
.modal-back {
  position: fixed; top: 0; left: 0; right: 0; bottom: 0;
  height: auto;
  z-index: 50;
  padding: 0;
}
.modal h2 { color: var(--bright); font-size: 1.05rem; margin: 0 0 8px; }
.modal p { color: var(--text); margin: 0 0 8px; }

/* ── Экран результата ── */
.result { text-align: center; margin-top: 56px; }
.result .big { font-size: 3rem; font-weight: 700; margin-bottom: 12px; }
.result .big.ok { color: var(--green); }
.result .big.fail { color: var(--red); }
.result .sub { color: var(--dim); margin-bottom: 28px; word-break: break-word; }
.result .spinner { color: var(--orange); }

/* ── Лента чата: вид обычного мессенджера (пузыри) ──
   Тот же контейнер .snapshot: на него завязаны раскладка screen-chat,
   прокрутка и автоскролл (chats.js). Отходим от моноширинного «снимка
   терминала» к обычному тексту и пузырям. Размер шрифта — по настройке
   (--chat-font из fontscale.js). */
.snapshot {
  background: transparent; border: none; border-radius: 0;
  padding: 4px 2px 8px;
  font-family: -apple-system, system-ui, "Segoe UI", Roboto, sans-serif;
  font-size: var(--chat-font, .9rem); line-height: 1.45; color: var(--text);
  white-space: normal; word-break: break-word;
  max-height: 52vh; overflow-y: auto; margin: 0 0 8px;
}
/* Ряд пузыря: свой — к правому краю, ассистента — к левому. */
.snapshot .row { display: flex; margin: 10px 0; }
.snapshot .row.me { justify-content: flex-end; }
.snapshot .row.them { justify-content: flex-start; }
.snapshot .bubble {
  max-width: 82%; padding: 9px 12px; border-radius: 16px;
  overflow-wrap: anywhere;
}
.snapshot .bubble-them {
  background: var(--panel); color: var(--bright); border-top-left-radius: 5px;
}
/* Свой пузырь — холодным акцентным тоном (перивинкль), чтобы сразу видеть, где твоё.
   position:relative + запас снизу (padding-bottom) — под метку доставки, которая
   лежит абсолютом в правом нижнем углу. Запас-полоса гарантирует, что последняя
   строка текста (в т.ч. длинная) не заезжает под галочки. */
.snapshot .bubble-me {
  position: relative;
  background: var(--accent-soft); color: var(--bright);
  border-top-right-radius: 5px; white-space: pre-wrap;
  padding-bottom: 22px;
}
/* Значок доставки в правом нижнем углу своей реплики — как в Телеграме: часики
   ожидания, одна галочка (отправлено — Мак принял), слитная двойная (доставлено —
   реплика есть в записях разговора) или часики + номер места в очереди «пиши,
   пока занят» (до Клода ещё не доехало). Знаки — ИКОНКИ
   (см. ICN.tick* в chats.js), не символы: только линиями получается слитный
   телеграмовский вид. Абсолют в углу; текст его не перекрывает благодаря
   padding-bottom пузыря. Размер привязан к тексту пузыря (0.82 от него), поэтому
   знак растёт вместе с выбранным размером шрифта чата. */
.snapshot .bubble-me .ticks {
  position: absolute; right: 12px; bottom: 7px;
  display: flex; align-items: center; gap: 3px;
  font-size: .82em; line-height: 1;
  color: var(--dim); opacity: 1;   /* приглушённый — отправлено / очередь / часики */
  white-space: nowrap;
}
.snapshot .bubble-me .ticks svg { display: block; height: 1em; width: auto; }
/* «Доставлено» (реплика найдена в записях разговора) — обе галочки горят акцентом
   перламутра: только это состояние выделено цветом, остальные приглушённые. */
.snapshot .bubble-me .ticks-two { color: var(--orange); }
.snapshot .bubble-me .ticks-queue .ticks-n { font-size: .92em; font-variant-numeric: tabular-nums; }
/* Кнопка «прочитать вслух» 🔊 рядом с ответом ассистента: маленькая, у нижнего
   края пузыря, без рамки. Во время чтения (.tts-on) — оранжевая (значок ⏹). */
.snapshot .row.them { align-items: flex-end; }
.tts-btn {
  flex: 0 0 auto; align-self: flex-end; margin-left: 6px;
  background: none; border: none; color: var(--dim);
  line-height: 0; padding: 4px 6px; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.tts-btn.tts-on { color: var(--orange); }
.tts-btn:disabled { opacity: .35; }
/* Нижняя полоска «Читаю…» — появляется только пока идёт озвучка (у строки ввода,
   под большой палец). Индикатор + регулятор скорости + «Стоп». */
/* Плеер прослушки: скруглённая перламутровая панель. Слева круглая пауза/плей,
   затем подпись «Читаю…» ИЛИ ползунок перемотки, скорость и круглый «стоп». */
.read-bar { display: flex; align-items: center; gap: 10px; margin-bottom: 8px;
  background: var(--panel); border: 1px solid var(--border); border-radius: 26px;
  padding: 6px 10px 6px 6px; }
.read-bar[hidden] { display: none; }   /* [hidden] надёжно скрывает flex-контейнер */
/* Круглая кнопка паузы/воспроизведения (перивинкль). */
.read-play { flex: 0 0 auto; width: 44px; height: 44px; min-width: 44px; border-radius: 50%;
  border: none; background: var(--orange); color: #000; cursor: pointer;
  font-size: 17px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center; }
.read-label { flex: 1 1 auto; min-width: 0; color: var(--orange); font-size: .9rem; }
.read-label[hidden] { display: none; }
/* Ползунок перемотки озвучки с Мака + время «0:12 / 1:05». Занимает гибкое место
   вместо подписи «Читаю…» (они взаимоисключающие). */
.read-seek-wrap { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 8px; }
.read-seek-wrap[hidden] { display: none; }
.read-seek { flex: 1 1 auto; min-width: 0; height: 24px; margin: 0;
  accent-color: var(--orange); background: transparent; cursor: pointer; }
.read-time-pos { flex: 0 0 auto; color: var(--dim); font-size: .8rem;
  font-variant-numeric: tabular-nums; white-space: nowrap; }
.read-rate { flex: 0 0 auto; min-width: 52px; text-align: center;
  font-variant-numeric: tabular-nums; background: none; color: var(--orange);
  border: 1px solid var(--border); border-radius: 999px; padding: 7px 12px; cursor: pointer; }
/* «Стоп» — круглая кнопка с красным значком. */
.read-stop { flex: 0 0 auto; width: 40px; height: 40px; min-width: 40px; border-radius: 50%;
  background: none; color: var(--red); border: 1px solid var(--border);
  padding: 0; font-size: 14px; line-height: 1; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; }
/* Маркдаун-lite внутри пузыря ассистента */
.snapshot .bubble .md-p { margin: 0 0 8px; }
.snapshot .bubble .md-p:last-child { margin: 0; }
.snapshot .bubble .md-h {
  margin: 2px 0 6px; font-size: 1.05em; font-weight: 600; color: var(--bright);
}
.snapshot .bubble .md-ul, .snapshot .bubble .md-ol { margin: 4px 0 8px; padding-left: 20px; }
.snapshot .bubble .md-ul li, .snapshot .bubble .md-ol li { margin: 2px 0; }
.snapshot .bubble .md-ul:last-child, .snapshot .bubble .md-ol:last-child { margin-bottom: 0; }
.snapshot .bubble .md-code {
  background: #000; border: 1px solid var(--border); border-radius: 5px;
  padding: 1px 5px; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: .86em;
}
.snapshot .bubble .md-pre {
  background: #101010; border: 1px solid var(--border); border-radius: 10px;
  padding: 10px 12px; margin: 6px 0 2px; overflow-x: auto;
  font-family: ui-monospace, Menlo, Consolas, monospace; font-size: .82em;
  line-height: 1.5; color: var(--bright); white-space: pre;
}
.snapshot .bubble .md-pre:last-child { margin-bottom: 0; }
/* Текст будущего письма внутри ответа: полоска слева, и всё. Ни заголовка, ни
   плашки — пометки «=== НАЧАЛО ТЕКСТА ПИСЬМА ===» с экрана убраны именно
   потому, что они лишняя надпись; подписать их своими словами значило бы
   вернуть ту же надпись другими буквами. */
.snapshot .bubble .mail-text {
  border-left: 2px solid var(--border); padding-left: 10px; margin: 2px 0;
}
.snapshot .bubble .mail-text:last-child { margin-bottom: 0; }
/* ТО САМОЕ ПИСЬМО, КОТОРОЕ УЙДЁТ ПО КОНВЕРТУ, — прямой вопрос владельца
   27 августа: «как я пойму, что отправится именно это, а не вся переписка».
   Отвечено БЕЗ ЕДИНОЙ НОВОЙ НАДПИСИ: у выбранного письма та же полоска слева,
   но акцентная и толще, а у прежних писем ленты она остаётся тусклой. Кого
   выбрала лента — считается одним местом (`_zhivoePismo`, js/feed.js), тем же,
   по которому виден сам конверт. */
.snapshot .row.mail-live .mail-text {
  border-left-width: 3px; border-left-color: var(--orange);
}
/* Дневник действий: приглушённый список под блоком ассистента */
.snapshot .acts { margin: 4px 0 2px 4px; display: flex; flex-direction: column; gap: 3px; }
.snapshot .act {
  display: flex; align-items: baseline; gap: 6px;
  color: var(--dim); font-size: .92em;
}
.snapshot .act .act-g { opacity: .8; flex: 0 0 auto; }
.snapshot .act-more {
  color: var(--dim); font-size: .92em; margin: 2px 0;
  display: flex; align-items: center; gap: 6px; cursor: pointer;
}
.snapshot .acts-more summary { list-style: none; }
.snapshot .acts-more summary::-webkit-details-marker { display: none; }
.snapshot .act-more .chev { display: inline-block; transition: transform .15s; }
.snapshot .acts-more[open] .act-more .chev { transform: rotate(90deg); }
.snapshot .act-hidden { margin: 3px 0 2px; display: flex; flex-direction: column; gap: 3px; }
/* Правил `.snapshot .working` / `.work-dots` здесь БОЛЬШЕ НЕТ. Они одевали узел
   «Работаю ...», который приехал в Офис из МЁРТВОГО кода старого приложения
   (`workingRow`, chats.js:899-906 — там его не зовёт никто). Живой индикатор
   старого — три круглые точки с волной укрупнения и БЕЗ слова, они лежат ниже
   (`.chat-dots`) и стоят последней строкой ленты. */
.refresh {
  background: none; border: 1px solid var(--border); color: var(--orange);
  border-radius: 10px; padding: 8px 14px; margin-bottom: 12px; cursor: pointer;
}

/* ── Карточка переданного файла (в ленте чата и в разделе «Файлы») ── */
.file-card {
  display: flex; align-items: center; gap: 12px;
  background: var(--panel); border: 1px solid var(--border); border-radius: 14px;
  padding: 14px; margin: 8px 0;
  /* лента чата — моноширинная и pre-wrap; карточку возвращаем к обычному тексту */
  white-space: normal; font-family: -apple-system, system-ui, sans-serif;
}
.file-card .file-icon { font-size: 1.6rem; flex: 0 0 auto; line-height: 1; }
.file-card .file-meta { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.file-card .file-name {
  color: var(--bright); font-size: 1rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.file-card .file-size { color: var(--dim); font-size: .8rem; }
.file-card .file-err { color: var(--red); font-size: .8rem; }
.file-card .file-actions { flex: 0 0 auto; display: flex; gap: 6px; align-items: center; }
.file-card .file-open,
.file-card .file-dl {
  flex: 0 0 auto; min-width: 0; padding: 9px 12px; font-size: .9rem;
}
.file-card .file-del {
  flex: 0 0 auto; min-width: 0; padding: 9px 11px; font-size: 1.05rem;
}

/* ── Звуковой файл: карточка с проигрывателем ──
   Две строки: сверху те же сведения и кнопки («Скачать», при необходимости
   корзина), снизу плеер. Оформление плеера намеренно повторяет нижнюю полоску
   озвучки (.read-bar выше): круглая кнопка перивинклевого цвета, ползунок с тем
   же accent-color, время моноширинными цифрами — чтобы в приложении был ОДИН
   узнаваемый плеер, а не два разных. */
.file-card-aud { flex-direction: column; align-items: stretch; gap: 10px; }
.file-card-aud .file-head { display: flex; align-items: center; gap: 12px; }
.file-card-aud .file-meta { flex: 1; min-width: 0; }
.aud-row { display: flex; align-items: center; gap: 10px; }
.aud-play {
  flex: 0 0 auto; width: 40px; height: 40px; min-width: 40px; border-radius: 50%;
  border: none; background: var(--orange); color: #000; cursor: pointer;
  padding: 0; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
}
.aud-play:disabled { opacity: .5; }
.aud-seek {
  flex: 1 1 auto; min-width: 0; height: 24px; margin: 0;
  accent-color: var(--orange); background: transparent; cursor: pointer;
}
.aud-seek:disabled { opacity: .45; }
.aud-time {
  flex: 0 0 auto; color: var(--dim); font-size: .8rem;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* Просадки под пальцем у этой карточки нет и без отдельного правила: карточка со
   звуком — обычная плашка (не кнопка), сторожка нажатий её не берёт, а палец,
   который живёт на ползунке, попадает в сам ползунок — ему отклика не полагается
   вовсе (см. canHold в js/press.js). Прежнее правило гасило css-эффект, которого
   в файле больше нет. */

/* ── Окно просмотра файла (поверх приложения, без сохранения на телефон) ──
   Живёт в общем окне (.modal, см. «ВСПЛЫВАЮЩЕЕ ОКНО» ниже): своей подложки и
   своей рамки у него больше нет — стекло, скругление, тень, крестик и закрытие
   нажатием мимо приходят оттуда же, откуда у остальных окон. Здесь остаётся
   только то, чем просмотр файла отличается: он шире, тянется по высоте и
   показывает картинку/страницу во всю свою середину. */
/* Столбик и предел высоты — из общего .modal.mw. Своей высоты (была 92vh) здесь
   больше нет: 92vh не знает про «бровь» и домашний индикатор, а общая формула
   про них знает. */
.modal.viewer {
  text-align: left;
  max-width: 720px; padding: 10px 0 0; overflow: hidden;
}
/* Середина просмотра распирается сама (.viewer-body), поэтому телу окна нужна
   только цепочка растяжения, без своей прокрутки. */
.viewer .mw-body {
  display: flex; flex-direction: column; min-height: 0;
  overflow: hidden; margin: 0; padding: 0;
}
/* Имя файла читаемое, а не служебная надпись: обычный кегль и яркий цвет. */
.viewer .mw-title {
  text-transform: none; letter-spacing: 0; font-size: 1rem; font-weight: 400;
  color: var(--bright);
}
.viewer-body {
  flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center;
  background: #0d0d0d; overflow: auto;
}
.viewer-img { max-width: 100%; max-height: 100%; object-fit: contain; display: block; }
/* Правил встроенной страницы (.viewer-frame) здесь БОЛЬШЕ НЕТ: документ в
   формате бумаги внутри рамки айфон не показывает вовсе, поэтому он открывается
   ссылкой отдельной страницей — см. js/filedeck.js и .file-show в js/filecard.js. */
.viewer-noprev { text-align: center; color: var(--dim); padding: 48px 24px; }
.viewer-noprev .file-icon { font-size: 3rem; }
.viewer-noprev p { margin: 12px 0 0; }
.viewer-bar {
  display: flex; gap: 10px; justify-content: flex-end;
  padding: 12px 16px; border-top: 1px solid var(--border);
}

/* Режим выбора файлов: панель сверху и выбираемая карточка (крупная, не список) */
/* Обычная панель (Обновить + Выбрать): один ряд, короткие кнопки — не переносится. */
.files-bar { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
/* Панель выбора: ДВА фиксированных ряда — стабильная высота при любом числе
   выбранных, список под ней не прыгает. */
.files-bar-sel { flex-direction: column; align-items: stretch; }
.files-bar-sel .files-bar-row { display: flex; align-items: center; gap: 8px; }
/* Кнопка-переключатель: min-width под самую длинную подпись («Выбрать все») —
   смена текста на «Снять все» не меняет ширину. */
.files-bar-sel .files-toggle { flex: 0 0 auto; min-width: 128px; }
/* Счётчик: фикс. ширина справа — «Выбрано: 1» и «Выбрано: 12» не двигают раскладку. */
.files-bar-sel .files-count {
  flex: 1 1 auto; min-width: 96px; text-align: right;
  color: var(--dim); font-size: .85rem; white-space: nowrap;
}
/* Ряд действий: «Удалить выбранные» тянется, «Отмена» фиксированная. */
.files-bar-sel .files-del-sel { flex: 1 1 auto; min-width: 0; padding: 10px 14px; font-size: .95rem; }
.files-bar-sel .files-cancel { flex: 0 0 108px; }
.file-card-sel { cursor: pointer; }
.file-card-sel.sel { border-color: var(--orange); background: rgba(138, 160, 240, .14); }
.file-card .file-check {
  flex: 0 0 auto; width: 24px; height: 24px; border-radius: 7px;
  border: 2px solid var(--border); display: inline-flex; align-items: center;
  justify-content: center; color: #000; font-size: .9rem; font-weight: 700;
}
.file-card .file-check.on { background: var(--orange); border-color: var(--orange); }
.row-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.row-actions .btn { flex: 1 1 30%; min-width: 96px; padding: 13px 10px; font-size: .95rem; }

/* ── Карточка вариантов ответа (Клод задал вопрос с меню) ──
   Раньше это был столбик отдельных кнопок с рамками — единственное место в
   приложении, где выбор из списка выглядел не так, как в меню действий и в
   выборе проекта. Теперь строки те же самые: класс .win-item (см. раздел про
   строку списка выше), поэтому высота, поля, тонкая черта между строками и
   подсветка нажатия приходят оттуда же, откуда у всех меню, — а не повторены
   здесь второй раз.
   Отличий у карточки ровно два, и оба смысловые: подсветка выбранного варианта
   (видно, что нажал) и акцентная строка «Своё» — это отказ от вариантов и
   переход к свободному ответу, а не ещё один вариант. */
.qbar { flex-direction: column; gap: 0; }
.qbar[hidden] { display: none; }   /* пустой ряд ответа не держит место */
.qbar .qbtn-own { color: var(--orange); font-weight: 600; }
/* Обратная связь нажатия: подсветить выбранную строку, погасить остальные. */
.qbar .qbtn-active {
  color: var(--orange); font-weight: 600;
  background: var(--accent-soft);   /* холодная перивинклевая подложка акцента */
}
.qbar .qbtn:disabled { opacity: .45; }
.qbar .qbtn-active:disabled { opacity: 1; }   /* нажатую не гасим — она подсвечена */
/* Значок нажатия (галочка «Мак принял» или точки «ещё идёт») — к ПРАВОМУ краю
   строки, а не впритык к подписи: варианты разной длины, и значок рядом с
   текстом гулял бы по строке от одного вопроса к другому. */
.qbar .qmark { flex: 0 0 auto; margin-left: auto; padding-left: 8px; font-weight: 700; }

/* ── Блок «В очереди (N)» (фича «пиши, пока занят») над строкой ввода ── */
.queue-bar { display: flex; flex-direction: column; gap: 6px; margin: 0 0 8px; }
.queue-bar[hidden] { display: none; }
.queue-head { color: var(--dim); font-size: .82rem; }
.queue-item {
  display: flex; align-items: center; gap: 8px;
  background: var(--panel); border: 1px solid var(--border); border-radius: 12px;
  padding: 8px 10px;
}
.queue-prev {
  flex: 1 1 auto; min-width: 0; color: var(--text); font-size: .88rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.queue-x {
  flex: 0 0 auto; width: 28px; height: 28px; min-width: 28px; border-radius: 50%;
  background: none; border: 1px solid var(--border); color: var(--dim);
  font-size: 16px; line-height: 1; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}

/* ── Бейдж разрешений ── */
.badge {
  background: var(--red); color: #fff; font-size: .75rem; font-weight: 700;
  min-width: 22px; height: 22px; border-radius: 11px; padding: 0 7px;
  display: inline-flex; align-items: center; justify-content: center;
  margin-right: 10px; cursor: pointer;
}
.badge[hidden] { display: none; }
/* Бейдж в шапке — самая срочная кнопка приложения, а пилюля всего 22px. Промежутки
   держит gap группы (внешний отступ снимаем), тач-цель добираем прозрачным
   расширением до 48×48 (22 + 13 + 13) — красная пилюля визуально остаётся как есть. */
.topbar-right .badge { margin-right: 0; position: relative; }
.topbar-right .badge::after { content: ''; position: absolute; inset: -13px; border-radius: 50%; }

/* Значка непрочитанных чатов (пузырёк в шапке) и подвешенного к нему списка в
   приложении больше нет — их убрали из разметки, а чаты с непрочитанным видны
   на главном экране числом у своей пилюли. Правила оформления (.chat-badge,
   .chat-badge-count, .drop-pop, .chat-drop-*) удалены как висячие: ни в одном
   файле приложения этих имён не осталось. */

/* ── Разрешения ── */
.perm-card {
  background: var(--panel); border: 1px solid var(--border); border-radius: 14px;
  padding: 14px; margin-bottom: 12px;
}

/* ── Карточка письма (фаза 2 почты) ──
   Плашка та же, что у карточки файла и у карточки разрешений: панель, рамка,
   скругление 14, поле 14. Третьего вида карточки в приложении не заводится —
   письмо это такое же сообщение в ленте, как выданный файл.
   Внутри ровно три строки, и больше их не будет: от кого, тема, одна строка сути
   (решение владельца 18 августа). Отправитель и тема в одну строку с многоточием
   — как имя файла в карточке файла: чужая тема длиной в абзац иначе развалила бы
   ленту. Суть — двумя строками, дальше многоточие.
   Вся карточка нажимается (переключение нити), поэтому палец и курсор. */
.mail-card {
  background: var(--panel); border: 1px solid var(--border); border-radius: 14px;
  padding: 14px; margin: 8px 0; cursor: pointer;
  /* лента чата — моноширинная и pre-wrap; карточку возвращаем к обычному тексту */
  white-space: normal; font-family: -apple-system, system-ui, sans-serif;
}
/* Строка отправителя: имя и метка ящика в одну строку.
   Ящиков у владельца два, а вкладка одна, — метка отвечает на «куда пришло».
   Обрезается ТОЛЬКО имя: метка стоит справа нерушимо, иначе у длинного имени
   она уехала бы за край ровно там, где нужна. */
.mail-card .mail-from {
  color: var(--bright); font-size: 1rem;
  display: flex; align-items: baseline; gap: 6px; min-width: 0;
}
.mail-card .mail-who {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
/* Метка ящика — приглушённая и мелкая: это различитель, а не надпись. */
.mail-card .mail-box {
  flex: none; color: var(--dim); font-size: .8rem; white-space: nowrap;
}

/* ── ПИСЬМО ТЯНЕТСЯ: КАРТОЧКА ДЕРЖИТ ПОДСВЕТКУ ──
   Жалоба владельца 19 августа, дословно: «нажимаю на письмо, меня прокручивает
   вниз, и непонятно, отработало ли касание». Так и было: тело письма едет из
   ящика секунду и дольше, а экран всё это время не отвечал ничем. Отклик
   сторожки касаний (js/press.js) снимается вместе с пальцем, то есть кончается
   раньше, чем начинается ожидание.
   Владелец выбрал из четырёх вариантов первый: карточка подсвечивается сразу и
   держит подсветку, пока письмо тянется. Ни надписей, ни кружков, ни полос —
   поэтому здесь только рамка акцентом и лёгкая заливка, без единого нового
   узла на карточке. Ставит и снимает признак экран разговора
   (js/screens/chat.js). */
.mail-card.mail-busy {
  border-color: var(--orange);
  background: var(--accent-soft);
}
.mail-card .mail-subj {
  color: var(--dim); font-size: .9rem; margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mail-card .mail-gist {
  color: var(--dim); font-size: .85rem; margin-top: 6px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.perm-summary {
  color: var(--bright); font-family: ui-monospace, Menlo, monospace; font-size: .9rem;
  margin-bottom: 12px; word-break: break-word; white-space: pre-wrap;
}
.perm-gist { color: #8a8f98; font-size: 14px; margin: 2px 0 8px; }
/* Строка неудачи под кнопками карточки: «ответ не дошёл — нажми ещё раз».
   Тот же размер, что у пояснения (.perm-gist), но красным — это не подсказка,
   а сообщение о том, что решение владельца до Мака НЕ доехало. */
.perm-err { color: var(--red); margin: 8px 0 0; }

/* ── Терминал ── */
.btn-stop { background: var(--red); color: #fff; font-weight: 600; }
.term-cwd { color: var(--dim); font-size: .8rem; font-family: ui-monospace, Menlo, monospace; margin-bottom: 10px; word-break: break-all; }
/* Статус доставки в чате (между перепиской и вводом). Постоянное место под одну
   строку — кнопки НЕ прыгают при появлении/исчезновении статуса. Без лишнего отступа. */
/* Строка состояния: слева надпись (Работаю/Свободен), справа контекст окна.
   Одна линия, прежняя высота — расстояние до кнопок не растёт. */
.chat-status-row { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin: 0 0 8px; min-height: 1.15em; }
.chat-status {
  color: var(--dim); font-size: .8rem; font-family: ui-monospace, Menlo, monospace;
  min-height: 1.15em; line-height: 1.15; margin: 0; word-break: break-word; min-width: 0;
  /* Текст надписи — под текстом переписки (рамка снимка: 1px border + 12px
     padding), чтобы не торчать левее читаемого текста. */
  padding-left: 13px;
}
/* Счётчик расхода окна из строки состояния УБРАН совсем (просьба владельца):
   в чате ему не место, расход виден в разделе «Лимиты и расход». */
/* Индикатор «идёт ход»: три точки в ФИКСИРОВАННЫХ слотах. Волна укрупнения бежит
   слева направо — увеличивается только активная точка через transform:scale (GPU,
   без reflow). Слоты не меняют размер и не двигаются, поэтому строка состояния и
   контекст справа стоят намертво (в отличие от прежних текстовых точек ::content,
   которые меняли ширину надписи и дёргали всю строку). Цвет — currentColor (--dim). */
.chat-dots { display: inline-flex; align-items: center; gap: 8px; height: 1.15em; vertical-align: middle; }
.chat-dots i {
  flex: 0 0 7px; width: 7px; height: 7px; border-radius: 50%;
  background: #9AA3AE;
  transform: scale(.55); transform-origin: center;
  animation: chat-dot-wave 1.5s ease-in-out infinite;
}
.chat-dots i:nth-child(2) { animation-delay: .25s; }
.chat-dots i:nth-child(3) { animation-delay: .5s; }
@keyframes chat-dot-wave {
  0%, 100% { transform: scale(.55); opacity: .4; }
  50%      { transform: scale(1);   opacity: 1; }
}
.chat-status.ok { color: #3fb950; }      /* доставлено — зелёный */
.chat-status.fail { color: var(--red); font-weight: 600; }   /* не доставлено — красный */
/* ── Строка ввода — «капсула» в два ряда (как поле ChatGPT) ──
   Вся строка ввода — одна скруглённая панель. СВЕРХУ поле ввода, СНИЗУ ряд
   управления: слева ＋ (файл), справа микрофон и «Отправить». Отдельной подложки
   под полем нет. */
.term-input-row {
  display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px;
  background: var(--panel); border: 1px solid var(--border);
  border-radius: 24px; padding: 10px 12px;
  /* Якорь для слоя записи: он лежит ПОВЕРХ содержимого капсулы, в её же полях
     (см. .rec-live ниже). Отдельного окна записи больше нет. */
  position: relative;
  /* Плавное загорание и угасание кромки при заходе в поле — см. правило ниже. */
  transition: border-color .22s ease;
}
/* ── КУРСОР В ПОЛЕ — ГОРИТ ТОЛЬКО КРОМКА КАПСУЛЫ ──
   Тем же языком, каким горит включённая пилюля авто-озвучки: перивинклевая
   обводка (--orange). ЗАЛИВКИ ВНУТРИ НЕТ И БЫТЬ НЕ ДОЛЖНО — владелец сказал это
   прямо. Подсветка поля (accent-soft на самой капсуле) читалась бы как «поле
   выделено/заблокировано», а нужно ровно одно: показать, куда сейчас попадёт
   набранный текст.
   Ловим фокус ИМЕННО У ПОЛЯ (:has(.term-input:focus)), а не у капсулы целиком
   (:focus-within): внутри капсулы живут микрофон, ＋, круг отправки и пилюля
   озвучки, и кромка загоралась бы от нажатия на любую из них — обещание не то.
   Второй селектор обязателен: в стеклянном облике кромка задана правилом с
   признаком вида, оно сильнее и без этой строки перебило бы подсветку. */
.term-input-row:has(.term-input:focus),
.term-input-row:has(.term-input:focus) {
  border-color: var(--orange);
}
/* Базовое поле — используется и в модалках (переименование/совет), там оно должно
   оставаться с рамкой и подложкой. Поэтому рамку/фон снимаем ТОЛЬКО внутри
   капсулы (.term-input-row .term-input ниже). */
.term-input {
  flex: 1; min-width: 0; background: #161616; color: var(--bright);
  border: 1px solid var(--border); border-radius: 10px; padding: 13px;
  font-family: ui-monospace, Menlo, monospace; font-size: .95rem; -webkit-appearance: none;
}
.term-input:focus { outline: none; border-color: var(--orange); }
.term-input:disabled { opacity: .6; }
/* Поле-строка сверху: без своей рамки/фона (сливается с капсулой). */
.term-input-row .term-input {
  width: 100%; background: none; border: none; border-radius: 0; padding: 6px 4px;
}
.term-input-row .term-input:focus { border: none; }
/* Нижний ряд управления. */
.term-controls { display: flex; align-items: center; gap: 8px; }
.term-spacer { flex: 1 1 auto; }
/* Круглая «Отправить» (↑) в стиле ChatGPT: заметный ДИСК (перивинкль при активном
   поле, приглушённый серый — на пустом), а стрелка внутри МАЛЕНЬКАЯ, с отступами
   (~половина диаметра). Диск делаем меньше тап-области (44px) через padding +
   background-clip:content-box — читается как отдельный кружок вровень с микрофоном.
   Единственный цветовой акцент ряда. */
.term-input-row .term-go {
  flex: 0 0 auto; width: 44px; height: 44px; min-width: 44px; min-height: 44px;
  padding: 6.5px; box-sizing: border-box; border-radius: 50%;
  background-clip: content-box; color: #141414; line-height: 0;
  display: inline-flex; align-items: center; justify-content: center;
}
.term-input-row .term-go svg { width: 26px; height: 26px; }   /* стрелка мелкая внутри диска ~31px */
/* «Стоп» — тот же диск на МЕСТЕ круга отправки (видна ровно одна из двух кнопок:
   пока Клод работает и писать нечего — «Стоп», иначе — «Отправить»). [hidden] сам
   бы не сработал: у .btn задан display, поэтому гасим явно. */
.term-input-row .term-stop {
  flex: 0 0 auto; width: 44px; height: 44px; min-width: 44px; min-height: 44px;
  padding: 6.5px; box-sizing: border-box; border-radius: 50%;
  background-clip: content-box; color: #141414; line-height: 0;
  display: inline-flex; align-items: center; justify-content: center;
}
.term-input-row .term-stop svg { width: 26px; height: 26px; }
.term-input-row .term-go[hidden],
.term-input-row .term-stop[hidden] { display: none; }
/* На поле с микрофоном (owner) диск приглушён (серый), пока пусто; перивинкль —
   в режиме .compose (есть текст/фото). background-color (не shorthand), чтобы не
   сбросить background-clip и не растянуть заливку на всю тап-область. */
.term-input-row.has-mic:not(.compose) .term-go {
  background-color: var(--border); color: var(--dim);
}
.chips-label { color: var(--dim); font-size: .75rem; margin: 0 0 6px; }
.chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.chip {
  background: var(--panel); border: 1px solid var(--border); color: var(--text);
  border-radius: 999px; padding: 7px 13px; font-size: .85rem;
  font-family: ui-monospace, Menlo, monospace; cursor: pointer;
}
.chip.fav { border-color: var(--orange); color: var(--orange); }
.term-out {
  background: #101010; border: 1px solid var(--border); border-radius: 12px;
  padding: 12px; font-family: ui-monospace, Menlo, monospace; font-size: .8rem;
  line-height: 1.4; color: var(--bright); white-space: pre-wrap; word-break: break-word;
  height: 44vh; overflow-y: auto;
}

/* ── Экран чата: устойчивая раскладка (не прыгает под потоком) ──
   Пока открыт диалог (#screen получает класс screen-chat из chats.js), тело
   страницы фиксируется по высоте окна, а экран становится колонкой, которая
   flex-ом заполняет всё место под шапкой (без магической высоты шапки в 52px —
   она хрупкая и ломалась о клавиатуру). Лента (снимок окна) занимает ВСЁ
   свободное место и прокручивается ВНУТРИ себя; ряд ввода (скрепка + поле +
   «Отправить») прибит к низу и не двигается — сколько бы Клод ни дописывал. */
.screen.screen-chat {
  flex: 1 1 auto;          /* заполняем всё место под шапкой */
  display: flex;
  flex-direction: column;
  overflow: hidden;
  min-height: 0;
  position: relative;      /* якорь для оверлея записи (position:absolute) */
  /* Базовый .screen добавляет снизу 16px + безопасную зону — в чате это лишний
     отступ, из-за которого ряд ввода висит над краём. В чате ряд ввода прижат к
     САМОМУ низу экрана без отступа: даже безопасную зону под домашний индикатор
     iPhone (env(safe-area-inset-bottom) ≈ 34px) не резервируем — Дмитрий просил
     панель вплотную к нижнему краю. Компромисс, если индикатор перекрывает
     кнопку «Отправить»: вернуть небольшой фикс. отступ (напр. 10px). */
  padding-bottom: 0;
}
/* ── ПОЧЕМУ СТРАНИЦА В ЧАТЕ БОЛЬШЕ НЕ ЗАПЕРТА ──
   БЫЛО: тело страницы прибивалось (position:fixed), обрезалось по высоте, а у
   html стоял запрет прокрутки. Замер на телефоне владельца (проба-близнец
   probe3.html, установленная на домашний экран) показал, что нижние 68 точек
   экрана мы отдавали САМИ, этой самой схемой:
     страница свободная (тело не прибито, документ прокручивается) — окно 912,
       прибитый элемент с bottom:0 доходит вплотную до нижнего края;
     + прибитое тело                                              — окно 844 сразу,
       прибитый элемент отрывается от края ровно на 68;
     + запрет прокрутки у html                                    — 844.
   68 — это ровно высота полосы с часами (верхнее безопасное поле). Жёсткая
   высота тела ни при чём: её добавление и снятие на числа не влияли.
   СТАЛО: тело свободное, документ прокручивается, а на видимое окно садится НЕ
   ТЕЛО, А ВНЕШНЯЯ ОБЁРТКА ЭКРАНА (#app-shell) — тем же приёмом и теми же
   числами, что раньше стояли на теле.
   ПОЧЕМУ ПИН ПЕРЕЕХАЛ НА ОБЁРТКУ, А НЕ НА КАПСУЛУ ВВОДА. Внутри обёртки не
   изменено НИ ОДНО правило: чат по-прежнему колонка, лента прокручивается внутри
   себя, капсула ввода по-прежнему последняя в потоке и прижата к низу колонки.
   Меняется ровно одно — низ обёртки теперь настоящий низ экрана, а не «низ окна»,
   и капсула опускается на те самые 68 точек. Прибей мы вместо этого саму капсулу,
   пришлось бы переучивать три узла разом: измеритель нижней стопки (--dock-h
   читает offsetTop, а у прибитого элемента offsetParent пуст и он выпал бы из
   замера), боковое меню (у обёртки при открытии стоят transform и clip-path — они
   подменяют точку отсчёта прибитым потомкам, и капсула прыгала бы на открытии) и
   всю компенсацию клавиатуры. Так же ничего этого не происходит.
   ЗАПАС ПРОКРУТКИ У ДОКУМЕНТА — обязательная часть лечения: полную высоту телефон
   отдаёт только СВОБОДНОЙ, прокручиваемой странице. Запас невидим: всё, что видно
   на экране, прибито (обёртка, полоска под часами, тень и затемнение меню, окна,
   подтверждения), поэтому прокрутка документа не двигает ни одного пикселя. Лента
   вдобавок не передаёт прокрутку наружу (overscroll-behavior у .snapshot ниже).
   ЧАСТНОГО СЛУЧАЯ ДЛЯ ЧАТА БОЛЬШЕ НЕТ. Обе части лечения — запас прокрутки у
   документа (min-height у body) и посадка обёртки на экран (position:fixed у
   #app-shell) — переехали в ОБЩИЕ правила в начале файла и действуют на всех
   экранах: главный, боковое меню и настройки внизу тоже доходят до физического
   края. Там же объяснено, почему запас считается от vh, а не от dvh.
   Здесь остались только правила самой раскладки чата и ветка клавиатуры. */
/* ОБЁРТКА И КЛАВИАТУРА. Два состояния:
   — КЛАВИАТУРА ЗАКРЫТА: работает общее правило #app-shell (top:0; bottom:0) —
     обёртка растянута между верхом и низом экрана, см. начало файла.
   — КЛАВИАТУРА ОТКРЫТА (body.kb-open, ставит chats.js): обёртка прибита к
     ВИДИМОМУ окну над клавиатурой по числам visualViewport — top = --vv-top
     (=offsetTop), height = --vv-height (=height). Зачем top: при фокусе iOS
     смещает видимое окно вниз (замер на устройстве: offsetTop=413) и скроллит
     документ; прибитые элементы считаются от окна раскладки, и без компенсации
     капсула уезжала выше экрана (замер: верх капсулы −135). Формула и числа
     перенесены с тела СЛОВО В СЛОВО, потому что геометрия та же: и тело, и
     обёртка — прибитый прямоугольник во всю ширину, внутри которого лежит одна и
     та же колонка чата. Этот узел ломали четырежды, переписывать его нечем.
     Высота при закрытой клавиатуре намеренно НЕ берётся из --vv-height: iOS
     иногда «роняет» финальный resize при закрытии, и одно кривое измерение
     схлопнуло бы чат в верхнюю половину экрана (это уже было). */
body.kb-open:has(.screen-chat) #app-shell {
  top: var(--vv-top, 0px);
  bottom: auto;
  height: var(--vv-height, 100dvh);
}
/* ВСПЛЫВАЮЩЕЕ ОКНО ПРИ ОТКРЫТОЙ КЛАВИАТУРЕ — ТУДА ЖЕ, КУДА И ОБЁРТКА ЧАТА.
   Окно с полем ввода (переименование, «Совет», правка записи словаря) поднимает
   клавиатуру, и дальше с ним происходит ровно то, что описано выше про капсулу:
   затемнение прибито (position: fixed), прибитое считается от окна РАСКЛАДКИ, а
   iOS в этот момент окно раскладки прокручивает — панель уезжает выше экрана.
   Лечим тем же способом и ТЕМИ ЖЕ ЧИСЛАМИ, которые узел клавиатуры уже
   выкладывает наружу: --vv-top и --vv-height. Сам узел (js/screens/chats.js) не
   трогаем — только читаем то, что он и так публикует; его ломали четырежды.
   Оговорка :has(.screen-chat) взята оттуда же и по той же причине: числа
   visualViewport ставит и обновляет диалог чата, и доверять им можно ровно там,
   где он живой. На прочих экранах правила нет — окно ведёт себя как раньше.
   Предел высоты панели тоже переезжает на видимое окно, иначе она осталась бы
   считать высоту по целому экрану и нижним краем ушла бы под клавиатуру.
   ПЕРЕЕЗЖАЕТ ТОЛЬКО ПОДЛОЖКА-ЛОВЕЦ. Само окно теперь прибито к экрану
   (position: fixed) и место ему считает сборщик числами (js/ui.js → mwPlace).
   Он же разбирает клавиатуру: открытие при поднятой клавиатуре ждёт, пока она
   уедет, а поднявшуюся уже после открытия разбирает сдвигом на перекрытие
   (mwKbFit) — прижима к верху экрана у окон с полем ввода больше нет.
   Отдельного предела высоты здесь тоже нет: потолок высоты один на все окна и
   стоит у сцены (см. раздел эталона в конце файла). */
body.kb-open:has(.screen-chat) .modal-back {
  top: var(--vv-top, 0px);
  bottom: auto;
  height: var(--vv-height, 100dvh);
}

/* Лента тянется на всё свободное место и прокручивается сама. min-height:0
   обязателен — иначе flex-ребёнок не даёт колонке сжать его, и вместо
   прокрутки лента распирала бы низ (ровно прежний баг «окно прыгает»). */
.screen.screen-chat > .snapshot {
  flex: 1 1 auto;
  min-height: 0;
  max-height: none;
  /* Докрутили ленту до края — дальше НИЧЕГО не прокручивается. У документа в чате
     теперь есть невидимый запас прокрутки (см. min-height тела выше), и без этой
     строки дотяг пальцем за конец ленты уходил бы в него: лента переставала бы
     пружинить, а «прокрутка в никуда» читается как подвисание. */
  overscroll-behavior: contain;
}
/* Всё, что выше и ниже ленты, держит свою высоту (не растягивается и не
   сжимается) — верх остаётся вверху, ряд ввода остаётся внизу. */
.screen.screen-chat > .screen-title,
.screen.screen-chat > .find-wrap,
.screen.screen-chat > .dialog-top,
.screen.screen-chat > .qbar,
.screen.screen-chat > .chat-status-row,
.screen.screen-chat > .read-bar,
.screen.screen-chat > .queue-bar,
.screen.screen-chat > .term-input-row {
  flex: 0 0 auto;
}
/* Полоса вложений — не сосед капсулы, а ЕЁ ПЕРВЫЙ РЯД (см. js/screens/chat.js).
   Капсула тоже колонка, поэтому ряду нужен тот же запрет на растяжение и сжатие:
   без него длинный список плашек ужался бы вместо того, чтобы прокручиваться. */
.term-input-row > .photo-previews { flex: 0 0 auto; }
/* Много ожидающих в очереди — блок прокручивается сам, а не выталкивает ряд ввода. */
.screen.screen-chat > .queue-bar:not([hidden]) { max-height: 26vh; overflow-y: auto; }
/* Нижний отступ капсулы ввода зависит от того, открыта ли клавиатура (класс
   kb-open ставит chats.js вместе с --vv-height). Логика ровно обратная по величине:
   — КЛАВИАТУРА ЗАКРЫТА: воздух над системной полоской = safe-area + 16px. Нижний
     ряд (＋, микрофон, круг отправки) не срезается краём и стоит с запасом.
   — КЛАВИАТУРА ОТКРЫТА: капсула вплотную к её верху, маленький зазор 6px. Сама
     клавиатура перекрывает домашний индикатор, поэтому safe-area тут не нужна —
     иначе получился бы большой пустой тёмный зазор над клавиатурой.
   Отступ вешаем на саму капсулу, а НЕ на padding-bottom экрана: капсула — самый
   низ разговора, и её поле должно зависеть от клавиатуры напрямую. */
.screen.screen-chat > .term-input-row {
  margin-bottom: calc(env(safe-area-inset-bottom) + 16px);
}
body.kb-open .screen.screen-chat > .term-input-row {
  margin-bottom: 6px;
}
/* Защита от переполнения: если вариантов ответа или выбранных файлов слишком
   много, они прокручиваются сами, а НЕ выталкивают ряд ввода за экран. Предел
   полосы вложений переехал вместе с ней внутрь капсулы: капсула растёт вверх, но
   не бесконечно — дальше прокручивается сама полоса. */
.screen.screen-chat > .qbar { max-height: 34vh; overflow-y: auto; }
.term-input-row > .photo-previews:not([hidden]) { max-height: 28vh; overflow-y: auto; }

/* ── Лимиты и расход ── */
.tabs { display: flex; gap: 8px; margin-bottom: 14px; }
.tab {
  flex: 1; background: var(--panel); border: 1px solid var(--border); color: var(--text);
  border-radius: 10px; padding: 11px 6px; font-size: .9rem; cursor: pointer;
}
.tab.active { background: var(--orange); color: #000; border-color: var(--orange); font-weight: 600; }
.limit { margin-bottom: 18px; }
.limit-head { display: flex; justify-content: space-between; color: var(--bright); margin-bottom: 7px; }
.limit-pct { color: var(--orange); font-weight: 600; }
.limit-reset { color: var(--dim); font-size: .8rem; margin-top: 6px; }
.bar { background: var(--border); border-radius: 6px; height: 10px; overflow: hidden; }
.bar-fill { background: var(--orange); height: 100%; border-radius: 6px; }
.stat { display: flex; justify-content: space-between; align-items: baseline; padding: 15px 0; border-bottom: 1px solid var(--border); }
.stat-label { color: var(--text); }
.stat-value { color: var(--bright); font-size: 1.2rem; font-weight: 600; }
.stale { color: var(--dim); font-size: .8rem; margin-bottom: 10px; font-style: italic; }
.usage-section { color: var(--dim); font-size: .72rem; font-weight: 600; margin: 22px 0 10px; text-transform: uppercase; letter-spacing: .1em; }

/* ── Сервер: стадии операции ── */
.stages { margin: 8px 0; }
.stage {
  color: var(--text); font-family: ui-monospace, Menlo, monospace; font-size: .9rem;
  padding: 8px 0; border-bottom: 1px solid var(--border);
}
.stage.done { color: var(--green); font-weight: 600; }
.stage.fail { color: var(--red); font-weight: 600; }

/* ── Партнёр: крупный блок контекста бота ── */
.ctx-card {
  background: var(--panel); border: 1px solid var(--border); border-radius: 16px;
  padding: 20px; margin-bottom: 14px; text-align: center;
}
.ctx-label { color: var(--dim); font-size: .85rem; margin-bottom: 6px; }
.ctx-big { color: var(--orange); font-size: 3rem; font-weight: 700; line-height: 1.1; margin-bottom: 14px; }
.ctx-card .bar { height: 14px; }
.ctx-sub { color: var(--text); font-size: .85rem; margin-top: 10px; min-height: 1em; }

/* ── Всплывающее короткое подтверждение ── */
/* Отсчитывается от НИЖНЕГО КРАЯ ЭКРАНА: прибитый элемент считает низ по холсту
   страницы, а холст занимает весь экран (см. --app-h в :root). Отступ — только
   безопасная зона под полоску жеста «домой» плюс воздух. Никаких поправок здесь
   быть не должно: и поле сверху (--safe-top), и снятая теперь самодельная
   компенсация уводили подтверждение на десятки точек — то вверх, то за край. */
.toast {
  position: fixed; left: 50%; bottom: calc(28px + env(safe-area-inset-bottom));
  transform: translateX(-50%); z-index: 60;
  background: var(--green); color: #000; font-weight: 600;
  padding: 12px 20px; border-radius: 12px; box-shadow: 0 4px 16px rgba(0,0,0,.4);
}
.toast.bad { background: var(--red); color: #fff; }
/* Нейтральное сообщение (не «получилось / не получилось», а справка вроде «скоро
   будет»): подложка панели, акцентный текст, тонкая обводка — та же гамма. */
.toast.info {
  background: var(--panel); border: 1px solid var(--border); color: var(--orange);
  font-weight: 600; max-width: calc(100vw - 40px); text-align: center;
}

/* ── Партнёр: пейджер из двух слайдов (Бот / Офис) ── */
.pager-viewport { overflow: hidden; width: 100%; }
.pager-track { display: flex; width: 200%; will-change: transform; }
.pager-panel { width: 50%; flex: 0 0 50%; }
.pager-dots { display: flex; justify-content: center; gap: 9px; margin-top: 16px; }
.pager-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--dim); transition: background .2s; cursor: pointer; }
.pager-dot.active { background: var(--orange); }

/* ── Живой офис отдела маркетинга (изометрическая сцена в SVG) ── */
.office-scene {
  margin-bottom: 16px; transition: filter .25s;
}
.office-scene svg { display: block; width: 100%; height: auto; }
.office-scene.scene-off { filter: grayscale(.7) brightness(.55); transition: filter .3s; }
.office-status { text-align: center; color: var(--bright); font-size: .92rem; margin-bottom: 14px; min-height: 1.2em; }
.office-status.warn-off { color: var(--dim); }
.office-status.warn-hung { color: #ff6b61; font-weight: 600; }
.office-feed { display: flex; flex-direction: column; gap: 8px; }
.feed-line {
  display: flex; align-items: center; gap: 10px;
  background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px;
}
.feed-line .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--orange); flex: 0 0 auto; }
.feed-line .txt { color: var(--bright); font-size: .88rem; flex: 1; }
.feed-line .when { color: var(--dim); font-size: .72rem; flex: 0 0 auto; }
.feed-empty { color: var(--dim); text-align: center; font-style: italic; padding: 10px; }
/* строка «прямо сейчас» (в работе): зелёная точка, мягко пульсирует (не моргает) */
.feed-line.current .dot {
  background: #34c759;
  animation: feed-pulse 1.5s ease-in-out infinite;
}
.feed-line.current .txt { color: #b9f0c6; }
@keyframes feed-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(52, 199, 89, .55); opacity: 1; }
  50%      { box-shadow: 0 0 0 6px rgba(52, 199, 89, 0); opacity: .55; }
}
/* новая верхняя строка ленты — плавно появляется (а не резко) */
.feed-line.enter { animation: feed-in .4s ease-out both; }
@keyframes feed-in {
  from { opacity: 0; transform: translateY(-10px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ── Бегущее многоточие у ИДУЩЕГО ДЕЙСТВИЯ ──
   Не загрузка экрана (её заменили заготовки ниже), а отклик на действие, которое
   владелец только что начал сам: «Обновляю…», «Очищаю…», «Расшифровываю…»,
   окно «завожу новый чат». Такую подпись убирать нельзя — без неё непонятно,
   принял ли телефон нажатие. */
.waiting { text-align: center; margin-top: 64px; color: var(--orange); }
.dots::after { content: ""; animation: dots 1.2s steps(4,end) infinite; }
@keyframes dots { 0%{content:""} 25%{content:"."} 50%{content:".."} 75%{content:"..."} 100%{content:""} }

/* ── Заготовки содержимого («скелетоны») ──
   Пришли на смену надписи-загрузчику: пока данные едут, экран показывает серые
   формы будущего содержимого. Величина и порядок совпадают с настоящими
   карточками (сборки — в js/ui.js), поэтому подстановка данных не двигает
   раскладку.
   Подсветка бежит по ::after бесконечной анимацией: узлы НЕ пересоздаются,
   движение непрерывное и без рывков — тот же приём, что у точек «работаю».
   Цвета — из гаммы: тело заготовки цветом обводки (--border), блик — --bright с
   малой прозрачностью (так же записаны плашки бокового меню). */
.sk {
  position: relative; overflow: hidden; flex: none;
  background: var(--border); border-radius: 7px;
}
.sk::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(90deg,
    rgba(242, 242, 244, 0) 0%, rgba(242, 242, 244, .07) 50%, rgba(242, 242, 244, 0) 100%);
  animation: sk-run 1.5s linear infinite;
}
@keyframes sk-run { from { transform: translateX(-100%); } to { transform: translateX(100%); } }
/* Уважаем системный запрет анимаций: формы остаются, бег подсветки выключается. */
@media (prefers-reduced-motion: reduce) { .sk::after { animation: none; } }
/* Своего display у обёртки НЕТ намеренно: div и так блочный, а явное
   display:block перебило бы встроенное правило [hidden]{display:none} — и набор
   заготовок нельзя было бы просто спрятать (это делает экран подключения). */
.sk-card {
  background: var(--panel); border: 1px solid var(--border); border-radius: 14px;
  padding: 14px; margin-bottom: 12px;
}

/* ── Экран подключения: сообщение «нет связи с Маком» ── */
.load-msg { text-align: center; margin-top: 40px; color: var(--orange); }

/* ── Кнопки нижнего ряда капсулы: ＋ (слева), микрофон и круг отправки (справа) ──
   Стиль ChatGPT: плюс и микрофон — НЕЙТРАЛЬНЫЕ контурные (без цветового акцента),
   акцент только у круга отправки. */
/* ＋ «прикрепить файл» — нейтральный контурный значок в тонкой круглой обводке
   (кружок ~36px через ::before, меньше тап-области — вровень с диском отправки). */
.term-input-row .term-clip {
  position: relative;
  flex: 0 0 auto; width: 44px; height: 44px; min-width: 44px; min-height: 44px; padding: 0;
  border-radius: 50%; border: none; background: none;
  line-height: 0; color: var(--bright);
  display: inline-flex; align-items: center; justify-content: center; }
.term-input-row .term-clip::before {
  content: ''; position: absolute; width: 31px; height: 31px; border-radius: 50%;
  border: 1.5px solid var(--border); }
.term-input-row .term-clip svg { position: relative; width: 24px; height: 24px; }
/* Микрофон — крупный нейтральный контурный значок (без заливки/обводки), вровень
   по величине с диском отправки. Пара «микрофон + круг» справа.
   КОНВЕРТ («отправить письмом») идёт ТЕМ ЖЕ ПРАВИЛОМ, а не своим похожим: он
   встаёт в этот же ряд вплотную к кругу отправки и обязан совпасть с соседями
   и величиной, и отступом, и цветом. Своих красок и надписей у него нет
   (правило владельца «чем меньше надписей на экране, тем лучше»). */
.term-input-row .term-mic,
.term-input-row .term-mail {
  flex: 0 0 auto; width: 44px; height: 44px; min-width: 44px; min-height: 44px; padding: 0;
  border-radius: 50%; border: none; background: none;
  line-height: 0; color: var(--bright);
  display: inline-flex; align-items: center; justify-content: center; }
.term-input-row .term-mic svg,
.term-input-row .term-mail svg { width: 31px; height: 31px; }
.term-input-row .term-mic:disabled { opacity: .5; }
/* Письма нет — конверта нет ВОВСЕ. Правило нужно отдельной строкой: `display`
   выше сильнее признака `hidden`, и без него спрятанный конверт остался бы на
   экране (та же строка стоит у круга отправки и у «Стоп»). */
.term-input-row .term-mail[hidden] { display: none; }
/* ── Пилюля авто-озвучки в нижнем ряду капсулы (рядом с ＋) ──
   Как чип «Auto» в ChatGPT: небольшая пилюля-переключатель. Сама кнопка —
   прозрачная тап-область высотой 44px (палец), видимая пилюля — вложенный span
   высотой 32px, поэтому ряд не растёт. Выключено — приглушённая обводка (--border,
   --dim); включено — перивинклевая подсветка (--orange + --accent-soft, та же пара,
   что у выбранных плашек и чипов). Отклик на нажатие — общий, от сторожки. */
.term-input-row .tts-auto {
  flex: 0 0 auto; height: 44px; min-height: 44px; padding: 0;
  border: none; background: none; cursor: pointer;
  display: inline-flex; align-items: center;
}
.tts-auto-pill {
  display: inline-flex; align-items: center; gap: 6px;
  height: 32px; padding: 0 12px; border-radius: 999px;
  border: 1.5px solid var(--border); color: var(--dim);
  font-size: .82rem; font-weight: 600; line-height: 1; white-space: nowrap;
}
.tts-auto.on .tts-auto-pill {
  border-color: var(--orange); color: var(--orange); background: var(--accent-soft);
}
.tts-auto-ic { display: inline-flex; line-height: 0; flex: 0 0 auto; }
.tts-auto-ic svg { width: 17px; height: 17px; }

/* ══════════ ЗАПИСЬ ГОЛОСА ЖИВЁТ В САМОЙ КАПСУЛЕ ВВОДА ══════════
   ОТДЕЛЬНОГО ОКНА ЗАПИСИ БОЛЬШЕ НЕТ. Решение владельца 11 августа 2026, дословно:
   «предложи вариант без какого-то окна, которое открывается — оно нахуй не
   нужно». До этого по ПОЯВЛЕНИЮ окна прошло четыре захода подряд, и все четыре
   владелец забраковал на живом айфоне: овал вместо окна, обрезанные бока,
   тормозящие углы, пустая коробка. Появляться теперь нечему — значит нечему и
   ломаться: капсула ввода и так стоит внизу экрана всегда, и по касанию 🎤 она
   просто МЕНЯЕТ СОДЕРЖИМОЕ.
   Отсюда ушли: место и поля окна, его сцена, вторая поверхность (она была общей
   с окнами — см. «.modal.mw» ниже), заготовки внутри него и ветка клавиатуры.

   ВЫСОТА КАПСУЛЫ ПРИ ЭТОМ НЕ МЕНЯЕТСЯ — ПО УСТРОЙСТВУ, А НЕ ПОДГОНКОЙ ЧИСЕЛ.
   Слой записи вынут из потока (position:absolute) и своего места в раскладке не
   занимает вовсе, а обычная начинка капсулы на время записи не убирается и не
   прячется признаком [hidden], а становится НЕВИДИМОЙ (visibility). Невидимое
   место занимает по-прежнему — значит высота та же и с вложениями, и без них, —
   а касаний не ловит: нажать на спрятанный микрофон сквозь слой нельзя.
   Это не мелочь: одна строка в замере высоты капсулы уже стоила ленте пустоты в
   пол-экрана (см. dockWatch в js/screens/chat.js). */
.rec-live {
  position: absolute; inset: 0;
  /* Ровно поля капсулы — иначе слой лёг бы на её кромку, а ✕ не встал бы туда,
     где стоит «＋». Наследование держит их вместе при любой правке капсулы. */
  padding: inherit;
  display: flex; align-items: center; gap: 10px;
}
.rec-live[hidden] { display: none; }
.term-input-row.rec-on > :not(.rec-live) { visibility: hidden; }
/* Середина — там же, где обычно поле ввода: волна и часы. Касание по ней бросает
   запись и уводит на клавиатуру (js/rec.js) — это место прежней кнопки «Аа». */
.rec-mid { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 10px; }
.rec-time {
  flex: 0 0 auto; color: var(--bright); font-variant-numeric: tabular-nums;
  font-family: ui-monospace, Menlo, monospace; font-size: .85rem;
}
/* ✕ слева — на месте «＋», той же тап-областью 44 и без своей заливки: капсула
   сама себе поверхность, а второй кружок внутри неё читался бы «кругом в круге». */
.rec-btn { position: relative; border: none; background: none; cursor: pointer; line-height: 1; }
.rec-x {
  flex: 0 0 auto; width: 44px; height: 44px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--text); font-size: 26px;
}
/* «Готово» справа — на месте микрофона и кружка отправки, единственный цветовой
   акцент ряда (как и кружок отправки, который она собой закрывает). */
.rec-done {
  flex: 0 0 auto; height: 36px; padding: 0 16px; border-radius: 18px;
  background: var(--orange); color: #000; font-weight: 700; font-size: .95rem;
}
/* ── ПЕРВАЯ СЕКУНДА: ВИД ЗАПИСИ УЖЕ СТОИТ, МИКРОФОНА ЕЩЁ НЕТ ──
   Между касанием 🎤 и открытым микрофоном проходит 60 мс на тёплом старте и до
   полутора секунд на холодном. Всё это время владелец обязан видеть, что
   нажатие засчиталось: капсула уже в виде записи, часы на нуле, волна ровной
   нитью. Останавливать при этом нечего — «Готово» погашено и касаний не ловит
   (не «на глаз серое», а по-настоящему). ✕ живой: передумать можно в любой миг.
   Заготовок (серых форм с подсветкой) здесь нет: они были начинкой окна записи
   и ушли вместе с ним — внутри капсулы закрывать нечего, настоящие часы и волна
   честно показывают ноль и тишину. */
.rec-live.rec-wait .rec-done { opacity: .45; pointer-events: none; }
/* ВОЛНА ПЛЯШЕТ ОТ ГОЛОСА, А НЕ ОТ ТАЙМЕРА И НЕ ОТ РАЗМЕРА КУСКОВ.
   Сначала полоски плясали чистой анимацией, никак не связанной со звуком: в
   разобранном сбое они бодро отработали 35 секунд над пустотой, и человек им
   поверил. Потом высоту давал размер приходящих кусков записи — но кодировщик
   пишет ровным потоком, размер от громкости не зависит, и получилась «одна
   палочка, говорю или нет». Потом была одна гладкая векторная дорожка — её
   владелец посмотрел и попросил заменить на образец с пляшущими палочками.

   ЧТО ЗДЕСЬ СЕЙЧАС (образец, который выбрал владелец):
   • много ТОНКИХ палочек (см. REC_BARS в js/rec.js) шириной 1.5px, разложенных
     по всей ширине с малым зазором. Прежние 28 толстых столбиков владелец
     забраковал дважды — читается как столбики, а не как волна;
   • симметрия от середины: палочка растёт вверх и вниз от центральной линии
     (align-items: center у ряда);
   • ТРИ ПОЯСА по размаху с затуханием к краям: середина .rec-bar, ближе к краям
     .md, крайние .sm — пропорции образца 70/50/25;
   • у КАЖДОЙ палочки СВОЯ случайная скорость колебания (0.2…0.7 с) — её ставит
     JS при сборке. Это ядро эффекта: с одинаковой скоростью получается строевой
     шаг, а вразнобой — живая пляска;
   • яркость меняется вместе с высотой (.35 → 1), колебание туда-обратно, плавное.
   Числа высот ужаты под рост капсулы (было 48/42/30/15 в окне во всю ширину),
   пропорции образца сохранены.

   ЧЕСТНОСТЬ — ЧЕМ МЫ ОТЛИЧАЕМСЯ ОТ ОБРАЗЦА. В образце волна пляшет сама по себе
   и со звуком не связана вовсе — ровно то враньё, которое мы дважды чинили.
   Здесь пляска задаёт только ФОРМУ, а РАЗМАХ целиком задаёт обёртка .rec-wave-amp:
   её сжатие по вертикали (scaleY) ставит JS по настоящему замеру громкости, и
   только когда пришли непустые куски записи. Тишина или мёртвая запись → scaleY(0)
   → все палочки схлопываются в ноль, как бы бодро ни крутилась их анимация.
   Видна остаётся только ровная нить (.rec-wave::after) — она и есть «тишина».

   ПЛАВНОСТЬ. Уровень громкости приходит ~8 раз в секунду (такт 130 мс) — для
   плавной пляски этого мало, поэтому саму пляску делает оформление (анимация),
   а громкостью меняется только размах, да и тот доезжает плавно (transition).
   Кадровой отрисовки (requestAnimationFrame) нет нарочно: в фоне айфон её
   замораживает, и волна вставала бы колом. Двигаются только transform и
   opacity — это делает видеоускоритель, без единого кадра в нашем коде.
   ХОДОМ ПОЯВЛЕНИЯ ЭТО НЕ ЯВЛЯЕТСЯ: пляска — сама волна, и в первый миг записи
   она стоит на нуле размаха, то есть не двигает ни одной точки. */
.rec-wave { position: relative; flex: 1 1 auto; min-width: 0; height: 34px; overflow: hidden; }
/* Нить тишины: тонкая линия по центру, видна ВСЕГДА. Нужна потому, что при
   нулевом размахе палочки схлопываются в ничто, а «пусто» человек читает как
   «волны нет вообще». Яркость та же, что у палочки в нижней точке (.35). */
.rec-wave::after {
  content: ''; position: absolute; left: 0; right: 0; top: 50%;
  height: 2px; margin-top: -1px; border-radius: 1px;
  background: var(--orange); opacity: .35; pointer-events: none;
}
/* Обёртка размаха — единственное место, куда попадает громкость. z-index, чтобы
   палочки шли поверх нити (иначе нить рисуется последней и мутит их). */
.rec-wave-amp {
  position: relative; z-index: 1;
  display: flex; align-items: center; justify-content: space-between;
  width: 100%; height: 100%;
  transform: scaleY(0);            /* до первых данных записи — ровная нить */
  transition: transform 170ms linear;
  will-change: transform;
}
/* Зазор между палочками не задан числом нарочно: ширина палочки постоянная,
   а промежутки растягивает space-between. Поэтому на узком и широком телефоне
   палочек одинаковое число, и волна всегда ровно по ширине середины. */
.rec-bar {
  flex: 0 0 auto; width: 1.5px; border-radius: .75px;
  background: var(--orange); height: 30px;
  animation-name: rec-wave-lg;
  animation-iteration-count: infinite;
  animation-timing-function: ease-in-out;
  animation-direction: alternate;
  animation-duration: .45s;        /* запасное значение: свою ставит JS каждой палочке */
}
.rec-bar.md { height: 21px; animation-name: rec-wave-md; }
.rec-bar.sm { height: 11px; animation-name: rec-wave-sm; }
/* Нижние точки взяты из образца: 15/70, 15/50, 10/25 — то есть в самом низу
   палочка не исчезает, а остаётся коротким штрихом. */
@keyframes rec-wave-lg { from { opacity: .35; transform: scaleY(.21); } to { opacity: 1; transform: scaleY(1); } }
@keyframes rec-wave-md { from { opacity: .35; transform: scaleY(.30); } to { opacity: 1; transform: scaleY(1); } }
@keyframes rec-wave-sm { from { opacity: .35; transform: scaleY(.40); } to { opacity: 1; transform: scaleY(1); } }
/* Верхний ряд диалога (Обновить + ⋯) — кнопки с оранжевой обводкой, чтобы
   читались как кнопки, а не сливались с фоном и перепиской. Отступ от снимка. */
.dialog-top { margin-bottom: 14px; align-items: center; justify-content: flex-end; }  /* только «⋯» — прижат вправо; «Обновить»/«Совет» уехали в меню */
.dialog-top .refresh-main { flex: 1 1 auto; }                /* большая основная (заливная) */
/* Меню действий окна — компактная модалка со списком (открывается из «⋯» в строке
   панели списка чатов).
   Полей по бокам у панели НЕТ намеренно: строки идут от края до края, как в
   системном меню, а от края их отбивают собственные поля строки (14). Двойной
   класс — чтобы перебить верхнее поле общего .modal.mw. */
.modal.win-pop { padding: 6px 0 8px; max-width: 320px; }
/* Запаса по бокам телу здесь не надо: строки и так не выходят за свою ширину, а
   с запасом список поехал бы вбок относительно названия. */
.win-pop .mw-body { margin: 0; padding: 0; }
/* ── ПОЛОСА ВЛОЖЕНИЙ — ПЕРВЫЙ РЯД ВНУТРИ КАПСУЛЫ ВВОДА ──
   Своего верхнего отступа у полосы нет: воздух над ней даёт сама капсула
   (padding 10), а расстояние до строки текста — её межрядный зазор (gap 6).
   Оставь прежние 8 сверху и снизу — и капсула поднималась бы на 16 точек
   впустую при каждом прикреплённом файле. */
.photo-previews { display: flex; flex-wrap: wrap; gap: 6px; padding: 0; }
/* Пустой ряд превью НЕ должен занимать место (display:flex перебивал [hidden]). */
.photo-previews[hidden] { display: none; }
/* РАЗМЕР ПЛАШКИ — 34 ТОЧКИ, ВДВОЕ МЕЛЬЧЕ ПРЕЖНИХ 64. Решение владельца при
   переезде внутрь капсулы: прежний квадрат поднимал поле ввода почти на треть
   экрана, стоило прикрепить два файла в два ряда. 34, а не ровно 32, — чтобы
   крестик удаления (15) уместился ВНУТРИ угла и остался пальцем попадаемым. */
.photo-thumb {
  position: relative; width: 34px; height: 34px; border-radius: 7px;
  background-size: cover; background-position: center; flex: 0 0 auto;
}
/* КРЕСТИК ЛЕЖИТ ВНУТРИ ПЛАШКИ, А НЕ ВЫПИРАЕТ ЗА УГОЛ (было top/right: −6).
   Две причины, обе появились от переезда:
   1. У полосы есть предел высоты с прокруткой (max-height + overflow-y), а
      прокрутка обрезает всё, что вылезло за края, — выпирающий крестик у
      верхнего ряда срезало бы наполовину;
   2. капсула скруглена на 24, и крестик крайней правой плашки вылезал бы за её
      дугу — расчёт по углу: точка (−5, −5) от края содержимого лежит уже
      снаружи скругления.
   Размер 15 при плашке 34 — крестик читается и не съедает саму миниатюру. */
.photo-x {
  position: absolute; top: 2px; right: 2px; width: 15px; height: 15px; padding: 0;
  border: none; border-radius: 50%; background: rgba(0, 0, 0, .72); color: #fff;
  font-size: 11px; line-height: 15px; text-align: center;
}
/* Пачка уехала, итог ещё не пришёл: полоса приглушена и не отвечает пальцу.
   Человеку видно, что отправка идёт, и при этом ни одной новой надписи; крестик
   в это время не снимает то, что уже в пути. */
/* Отступ снизу — это МЕСТО ПОД ЧЕРТУ ХОДА (см. ниже). Без него черта легла бы
   поверх нижнего края плашек и читалась бы как подчёркивание имени файла. Пять
   точек берутся только на время отправки, когда полоса и так меняется. */
.photo-previews.sending { position: relative; opacity: .5; pointer-events: none;
  padding-bottom: 5px; }
/* ДОЛЯ ОТПРАВЛЕННОГО — ЧЕРТОЙ, А НЕ НАДПИСЬЮ. Большой файл уезжает кусками
   (js/kuski.js) и на сотовой сети идёт минутами: неподвижная приглушённая полоса
   за это время неотличима от зависшего приложения. Доля рисуется тем же, чем
   нарисован остаток лимита в «Расходе» (.lz-ring-arc — дуга по проценту), и тем
   же цветом; новых надписей не заводится — их в Офисе и так минимум.
   Ширину ставит сам тракт (--hod, 0…1). Доли нет (мелкий файл, одна команда) —
   запасной 0, и черты не видно вовсе: полоса выглядит ровно как прежде.
   Черта стоит ВНУТРИ полосы (bottom: 0), а не под ней: у полосы есть предел
   высоты с прокруткой (max-height + overflow-y выше), и всё вылезшее за край
   она обрезает. */
.photo-previews.sending::after {
  content: ''; position: absolute; left: 0; bottom: 0; height: 2px;
  width: calc(var(--hod, 0) * 100%);
  background: var(--orange); border-radius: 2px;
}
/* Плашка НЕ-картинки в полосе вложений: значок и имя — РОВНО ТЕ ЖЕ, что на
   карточке переданного файла (см. .file-card выше). В старом приложении
   прикрепить можно было только фото, поэтому такой плашки там нет вовсе; вид
   согласован с уже написанной карточкой, чтобы приложение не выглядело двумя
   разными.
   ЛЕЖАЧАЯ, А НЕ КВАДРАТНАЯ. Пока плашка была 64×64, имя вставало под значком в
   две строки и читалось. На 34 точках в квадрат помещалось бы три буквы —
   «смета.pdf» превратилось бы в «сме…», то есть в ничто. Поэтому у файла та же
   ВЫСОТА, что у миниатюры фото, а ширина по имени: значок слева, имя одной
   строкой справа. Предел ширины держит ряд от одной плашки во весь экран. */
.photo-thumb.photo-file {
  display: flex; flex-direction: row; align-items: center; justify-content: flex-start;
  width: auto; max-width: 150px; gap: 5px; padding: 0 22px 0 7px; overflow: hidden;
  background: var(--panel); border: 1px solid var(--border);
}
.photo-thumb .file-icon { font-size: .95rem; line-height: 1; }
/* Имя — ОДНОЙ строкой с отточием. Поле справа под крестик (padding-right 22)
   отбито у самой плашки, поэтому имя под крестик не заезжает. */
.photo-thumb .file-name {
  color: var(--bright); font-size: .72rem; line-height: 1.2;
  overflow: hidden; white-space: nowrap; text-overflow: ellipsis; text-align: left;
}

/* «Чаты»: верхний ряд (Новый Клод + Обновить) и меню «⋯» действий над окном. */
.chats-top { display: flex; gap: 8px; align-items: flex-start; }   /* малая кнопка по верхней линии */
.chats-top .new-claude { flex: 1 1 auto; }
/* Подвешенное к кнопке меню (.win-menu-wrap/.win-menu) отсюда убрано: в коде оно
   давно не собирается — меню действий стало обычным окном (.win-pop), потому что
   подвешенное на iPhone не закрывалось нажатием мимо. */
/* ── СТРОКА СПИСКА — ПО ОБРАЗЦУ СИСТЕМНОГО МЕНЮ АЙФОНА ──
   Владелец прислал снимок системного контекстного меню и сказал «хочу так везде».
   Из снимка сняты три числа, и они здесь: высота строки 56, значок 22, подпись
   обычным весом. Значок слева, подпись рядом — ровным левым краем по всему
   списку (место под значок держится даже без него, см. .wi-ic).
   Геометрия живёт в ОБЩЕЙ части файла, а не в ветке стекла: список строк
   одинаков в обоих обликах, стекло меняет только поверхность под ним. */
.win-item {
  position: relative;                    /* опора для черты-разделителя (::before) */
  display: flex; align-items: center; gap: 13px;
  width: 100%; text-align: left; background: none; border: none;
  color: var(--bright); padding: 8px 14px; min-height: 56px; font-size: 1.05rem;
  font-family: inherit; -webkit-appearance: none;
  border-radius: 10px; cursor: pointer;
}
/* ЗАЛИВКИ ВСЕЙ СТРОКИ ПРИ НАЖАТИИ НЕТ. Именно она и была тем «грубым
   прямоугольником», на который указал владелец: строка растянута во всю ширину
   панели, и плашка на ней читается как ошибка, а не как отклик. Теперь у строки
   общий отклик приложения: она проседает, а акцентом заливается ТОЛЬКО круг под
   значком слева — ровно как у кнопок шапки (сторожка js/press.js находит место
   значка сама, см. .wi-ic). Подпись при этом не трогается. Ни одной строки
   разметки для этого не нужно. */
/* Место под значок ЕСТЬ ВСЕГДА, даже когда значка не дали: иначе строки со
   значком и без разъехались бы по левому краю, а ровный левый край подписей —
   это половина сходства с системным меню. Цвет наследуется от строки, поэтому в
   опасной строке значок краснеет вместе с подписью сам. */
.wi-ic {
  flex: 0 0 22px; width: 22px; height: 22px;
  display: inline-flex; align-items: center; justify-content: center;
}
.wi-ic svg { display: block; width: 22px; height: 22px; }
/* Подпись занимает остаток строки; длинное имя проекта переносится, и строка
   становится выше — 56 это МИНИМУМ высоты, а не жёсткий размер. */
.wi-tx { flex: 1 1 auto; min-width: 0; }
/* Тонкая черта между строками — НЕ рамкой строки, а отдельной полоской: рамка
   шла бы во всю ширину до самого края панели, а в образце черта от краёв
   отбита. Полоска лежит по верхней грани каждой строки, кроме первой. */
.win-item + .win-item::before {
  content: ''; position: absolute; left: 14px; right: 14px; top: 0; height: 1px;
  background: var(--border); pointer-events: none;
}
/* Стрелка «›» в строке списка — признак «ведёт дальше», во вложенный список
   (например «Разработка» в окне нового чата). Прижата к правому краю и приглушена,
   как у плиток на главном экране, поэтому читается одинаково во всём приложении. */
.win-item .chev { flex: 0 0 auto; margin-left: 4px; color: var(--dim); }
.win-item.danger { color: var(--red); }
.win-item.disabled { opacity: .4; color: var(--dim); }
/* «Стоп» в активном состоянии (Клод работает) — налит красным. */
.win-item.stop-active { background: var(--red); color: #fff; font-weight: 600; }

/* ── Регулятор размера шрифта ленты (Настройки) ── */
.font-row { display: flex; align-items: center; gap: 10px; margin: 4px 0 8px; }
.font-step {
  flex: none; min-width: 56px; padding: 12px 14px;
  background: var(--panel); border: 1px solid var(--border); color: var(--text);
  border-radius: 12px; font-size: 1.1rem; font-weight: 600; cursor: pointer;
}
.font-step:disabled { opacity: .35; cursor: default; }
.font-sample { flex: 1; min-width: 0; margin: 0; max-height: none; }

/* ── Подсказка под настройкой ── */
.setting-hint { color: var(--dim); font-size: .8rem; margin: 6px 2px 4px; }
/* ── Заголовок ГРУППЫ настроек («Внешний вид») ──
   Внутри группы у каждой настройки свой заголовок (.usage-section, мелкие
   прописные). Если бы группа была написана тем же стилем, два уровня слились бы
   в один список без начала и конца. Поэтому группа — обычным крупным словом
   светлым тоном: видно, что дальше идёт несколько настроек про одно и то же.
   СЕЙЧАС ПРАВИЛО НИКЕМ НЕ ИСПОЛЬЗУЕТСЯ: группы стали раскрывающимися разделами
   (.sgroup ниже), и их заголовок рисуется строкой-кнопкой. Оставлено как образец
   размера и тона, от которого отталкивается .sgroup-head. */
.settings-group {
  color: var(--bright); font-size: 1.02rem; font-weight: 600;
  margin: 18px 0 2px; letter-spacing: .01em;
}

/* ── РАСКРЫВАЮЩИЙСЯ РАЗДЕЛ настроек ──
   Экран настроек вырос до простыни, и владелец попросил свернуть его в разделы.
   Закрытый экран — ровный столбик одинаковых строк-заголовков; по нажатию
   раздел раскрывается на месте, соседи остаются как были.
   Строка сделана в языке плиток меню (.tile): та же подложка, та же тонкая
   обводка, тот же радиус и та же высота под палец. Ни теней, ни объёма —
   владелец только что попросил плоский вид.
   Стрелка «›» — тот же признак «здесь есть продолжение», что у строк списка и
   плиток; при раскрытии она поворачивается вниз (как у «Ещё» в дневнике
   действий). */
.sgroup { margin: 0 0 10px; }
.sgroup-head {
  display: flex; align-items: center; gap: 10px;
  width: 100%; text-align: left;
  background: var(--panel); color: var(--bright);
  border: 1px solid var(--border); border-radius: 14px;
  padding: 16px 18px; min-height: 56px;
  font-family: inherit; font-size: 1.02rem; font-weight: 600;
  letter-spacing: .01em; cursor: pointer; -webkit-appearance: none;
}
.sgroup-name { flex: 1 1 auto; min-width: 0; }
.sgroup-chev {
  flex: 0 0 auto; color: var(--dim); font-size: 1.15rem; line-height: 1;
  transition: transform .24s ease, color .18s ease;
}
/* Крашеного нажатия здесь тоже нет — по той же причине и тем же решением, что у
   плитки (.tile выше): строка-заголовок раздела сделана «в языке плиток», и отклик
   у неё обязан быть тот же. Оранжевая обводка вокруг строки была таким же
   прямоугольником, только контурным. Отклик даёт сторожка js/press.js. */
.sgroup.sgroup-open .sgroup-chev { transform: rotate(90deg); color: var(--orange); }
/* Тело раздела. overflow: hidden нужен не только для обрезки на ходу — он же
   не даёт отступам верхнего и нижнего пунктов вылезти наружу, поэтому высоту
   можно мерить прямо по этому узлу (см. section() в js/screens/settings.js).
   ВНУТРЕННИХ ОТСТУПОВ ЗДЕСЬ БЫТЬ НЕ ДОЛЖНО: при вёрстке «по внешней рамке»
   (box-sizing: border-box на всём приложении) закрытый раздел не может стать
   ниже собственных полей, и каждый из шести заголовков подпирала бы лишняя
   полоска пустоты. Воздух под последним пунктом даёт отступ самого раздела. */
.sgroup-body {
  overflow: hidden; max-height: 0;
  transition: max-height .3s ease;
}
/* Системный запрет анимаций: раздел открывается и закрывается мгновенно. */
@media (prefers-reduced-motion: reduce) {
  .sgroup-body { transition: none; }
  .sgroup-chev { transition: none; }
}

/* Выбор голоса озвучки: нативный select во всю ширину + кнопка «Прослушать». */
.tts-voice-select {
  flex: 1 1 auto; min-width: 0; background: #161616; color: var(--bright);
  border: 1px solid var(--border); border-radius: 10px; padding: 12px;
  font-size: .95rem; -webkit-appearance: none; appearance: none;
}
.tts-voice-select:focus { outline: none; border-color: var(--orange); }

/* ── Поле даты «Подписка оплачена до» (Настройки) ──
   Нативный ввод даты (на айфоне — системный барабан), рядом «Сбросить». Высота с
   запасом от 44 пунктов: это тач-цель, а не курсорное поле. */
.date-row { display: flex; align-items: center; gap: 10px; margin: 4px 0 8px; }
.date-input {
  flex: 1 1 auto; min-width: 0; min-height: 46px;
  background: var(--panel); color: var(--bright);
  border: 1px solid var(--border); border-radius: 12px; padding: 12px;
  font-size: .95rem; font-family: inherit; -webkit-appearance: none; appearance: none;
}
.date-input:focus { outline: none; border-color: var(--orange); }
.date-clear {
  flex: 0 0 auto; min-height: 46px; padding: 12px 14px;
  background: none; border: 1px solid var(--border); color: var(--text);
  border-radius: 12px; font-size: .9rem; font-family: inherit; cursor: pointer;
  -webkit-appearance: none;
}
/* Своей заливки при нажатии нет: отклик у кнопки общий — просадка плюс заливка
   всей формы акцентом (сторожка js/press.js), см. .tile выше. */

/* ── Ползунок непрерывной настройки (Настройки → «Голос») ──
   Пауза между фразами и пауза в конце фразы — это не выбор из двух-трёх плиток,
   а два десятка значений с шагом в сотые доли секунды. Поэтому ползунок, а рядом
   ЧИСЛО СЛОВАМИ («0,2 секунды»): голая цифра владельцу ничего не говорит.
   Дорожка и бегунок рисуются вручную: системный вид ползунка на тёмной подложке
   почти не виден, а бегунок мельче пальца. */
.range-row { display: flex; align-items: center; gap: 14px; margin: 6px 0 4px; }
.range-input {
  flex: 1 1 auto; min-width: 0; height: 40px; margin: 0;
  background: none; -webkit-appearance: none; appearance: none;
}
.range-input:focus { outline: none; }
.range-input::-webkit-slider-runnable-track {
  height: 6px; border-radius: 999px;
  background: var(--panel); border: 1px solid var(--border);
}
.range-input::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 28px; height: 28px; border-radius: 50%; margin-top: -12px;
  background: var(--orange); border: none;
}
.range-input::-moz-range-track {
  height: 6px; border-radius: 999px;
  background: var(--panel); border: 1px solid var(--border);
}
.range-input::-moz-range-thumb {
  width: 28px; height: 28px; border-radius: 50%;
  background: var(--orange); border: none;
}
/* Число справа — фиксированной ширины, иначе ползунок дёргается на каждом шаге
   (у «1 секунда» и «0,25 секунды» разная длина). */
.range-val {
  flex: 0 0 auto; min-width: 118px; text-align: right; white-space: nowrap;
  color: var(--bright); font-size: .95rem; font-variant-numeric: tabular-nums;
}
/* Кнопка «Послушать пример» — во всю ширину, как плитки настроек над ней. */
.sample-btn { display: block; width: 100%; margin-top: 4px; }

/* ── Словарь произношения — ОТДЕЛЬНЫЙ ЭКРАН (решение владельца) ──
   В старом приложении это было всплывающее окно из настроек, и половина правил
   ниже описывала окно: предел ширины, свой столбик, своя прокрутка внутри и
   «+ запись», прибитая к низу окна. Экрану всё это не нужно — он и есть столбик
   во всю ширину, прокручивается страницей, а кнопка стоит там же, где кончается
   список. Поэтому от прежних правил осталось только то, что описывает САМИ
   ЗАПИСИ; оболочка окна убрана целиком. */
.dict-screen .srch-field { min-height: 46px; margin: 4px 0 2px; padding: 0 4px 0 16px; }
.dict-screen .srch-field[hidden] { display: none; }
.dict-screen .srch-input { font-size: 1rem; padding: 12px 0; }
.dict-count { color: var(--dim); font-size: .8rem; margin: 0 2px 4px; }
/* Список записей. Своей прокрутки нет — прокручивается весь экран. */
.dict-body { margin: 4px -4px 0; padding: 0 4px; }
/* Строка записи: слева слово и произношение (нажатие — правка), справа два
   кружка-действия. Кружки отдельными кнопками нарочно: «послушать» и «удалить»
   не должны срабатывать промахом по строке. */
.dict-row { display: flex; align-items: center; gap: 6px; }
.dict-row + .dict-row { border-top: 1px solid var(--border); }
.dict-text {
  flex: 1 1 auto; min-width: 0; display: block; text-align: left;
  background: none; border: none; color: var(--bright);
  padding: 12px 6px; border-radius: 10px; cursor: pointer;
  font-size: 1rem; font-family: inherit; -webkit-appearance: none;
}
/* Своей заливки при нажатии нет: отклик даёт сторожка — см. .tile. */
.dict-word, .dict-read {
  display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Произношение — второй строкой и приглушённо: главное в записи слово, по нему
   владелец её и находит глазами. Ударение в обоих полях показано ЗАГЛАВНОЙ
   буквой (в файле оно записано невидимым знаком, см. js/screens/dictionary.js). */
.dict-read { color: var(--dim); font-size: .88rem; margin-top: 2px; }
.dict-act {
  flex: 0 0 auto; width: 40px; height: 40px; border-radius: 50%;
  border: none; background: none; color: var(--dim); cursor: pointer;
  font-size: .95rem; line-height: 1; padding: 0; -webkit-appearance: none;
  font-family: inherit;
}
/* «+ запись» — во всю ширину, сразу под списком. */
.dict-add { width: 100%; margin-top: 10px; }

/* Экран «Архив» берёт вид словаря целиком: та же строка, тот же кружок действия
   справа. Своё здесь ровно одно правило — ячейка имени. Имя в этом списке НЕ
   кнопка (возвращает кружок справа), поэтому от .dict-text ей нужны доли ширины
   и отступы, но не палец: живой на вид, но мёртвый текст хуже, чем просто текст. */
.ar-name { flex: 1 1 auto; min-width: 0; padding: 12px 6px; color: var(--bright); }

/* ── Окно правки одной записи ── */
.modal.dict-edit { text-align: left; max-width: 380px; }
.dict-edit .usage-section { margin: 12px 0 6px; }
.dict-input {
  width: 100%; min-height: 46px;
  background: var(--panel); color: var(--bright);
  border: 1px solid var(--border); border-radius: 12px; padding: 12px;
  font-size: 1rem; font-family: inherit; -webkit-appearance: none;
}
.dict-input:focus { outline: none; border-color: var(--orange); }
/* Кнопки нижнего ряда делят ширину поровну: у окна она уже, чем заложенные в
   .btn 110 пунктов на кнопку. */
.dict-edit .btn-row .btn { min-width: 0; padding: 16px 8px; }
.dict-edit .btn-row { margin-top: 14px; }
/* «Слушать» — во всю ширину, прямо под полем «Как читается». */
.dict-listen { display: block; width: 100%; margin-top: 6px; }
/* Ответ Мака на сохранение. Отказ — КРАСНЫМ и словом в слово, как его написал
   Мак: там объяснено, что именно не так с записью (например «знак ударения
   стоит не на гласной»), и пересказывать это своими словами нельзя. */
.dict-msg { color: var(--dim); font-size: .88rem; margin: 10px 2px 0; min-height: 1.2em; }
.dict-msg.bad { color: var(--red); }
/* В СПИСКЕ та же строка стоит пустой почти всегда (она про отказ по
   «послушать») — пустая не занимает места и не отодвигает «+ запись». */
.dict-screen .dict-msg { min-height: 0; margin: 6px 2px 0; }
.dict-screen .dict-msg:empty { display: none; }

/* ── Боковое меню — ВЫДВИЖНАЯ панель слева (макет «Ночной перламутр») ── */
/* Приём: панель лежит слева и НЕ двигается, а уезжает и уменьшается сам основной
   экран (#app-shell, см. правила .km-on выше) — панель открывается из-под него.
   Крестика нет: закрытие — тап по видимой полоске уехавшего экрана справа.
   Слои снизу вверх: панель (0) → подкладка-тень .km-shade (1) → обёртка основного
   экрана (2). В закрытом состоянии обёртка накрывает и панель, и тень целиком.
   Панель едет параллаксом (см. transform ниже), тень — только проявляется.
   Высоту задаём ДВУМЯ ГРАНЯМИ: top:0 + bottom:0. Промежуточно тут стояло число
   --app-h — потому что нижняя грань прибитых элементов до нижнего края экрана не
   доставала: телефон занижал сообщаемую высоту на высоту полосы с часами, и
   панель кончалась раньше экрана (та самая пустая полоса под меню). Причина той
   недостачи найдена и убрана (см. раздел про раскладку чата), низ прибитым теперь
   доступен, и грань надёжнее числа: она совпадает с низом уехавшей карточки при
   любом решении телефона. Процентов и flex:1 тут нет: в iOS standalone-PWA у
   fixed-предка проценты считаются от auto, колонка схлопывалась по контенту и
   футер уезжал вверх.
   Ширина = --km-w (ровно та, на которую сдвигается основной экран). */
.km-root {
  position: fixed; top: 0; left: 0; bottom: 0; right: auto;
  height: auto;
  width: var(--km-w); z-index: 0;
  background: var(--bg);
  display: flex; flex-direction: column;
  /* ПАРАЛЛАКС. Панель больше не стоит неподвижно: она подтягивается слева следом
     за уезжающим экраном, но МЕДЛЕННЕЕ — стартует смещённой влево на треть своей
     ширины и приходит в ноль ОДНОВРЕМЕННО с экраном (одна длительность, одна
     кривая). При закрытии — тем же ходом обратно.
     Треть, а не половина: при большем отставании у левого края панели на миг
     показывалась бы чёрная полоса тела. */
  transform: translateX(calc(var(--km-w) / -3));
  transition: transform var(--km-ms) var(--km-ease);
}
body.km-on .km-root { transform: translateX(0); }
/* Уехавшая вправо обёртка выходит за правый край тела. Обычного overflow-x:hidden
   мало: он ЛИШЬ прячет полосу прокрутки, а тело остаётся ПРОКРУЧИВАЕМЫМ по
   горизонтали (замер: body.scrollLeft удалось увести на 273) — и системная «доводка
   сфокусированного поля до видимого» могла бы сдвинуть весь экран боком, причём
   сдвиг остался бы и после закрытия. overflow-x:clip обрезает БЕЗ создания
   прокрутки — прокручивать нечего.
   Оговорка: на экране ЧАТА у тела стоит overflow:hidden по обеим осям, а движки по
   правилам каскада возвращают clip к hidden, если вторая ось прокручиваемая, — там
   это правило бессильно. Поэтому боковой сдвиг тела дополнительно прибит в menu.js
   (слушатель scroll + сброс при закрытии); здесь — первая линия защиты для остальных
   экранов. Движки без clip тихо остаются на прежнем overflow-x:hidden. */
body.km-on, body.km-closing { overflow-x: clip; }
.km-menu {
  /* На всю высоту .km-root через position:absolute + inset:0, а НЕ через flex:1 /
     height:100%. Процентная высота и flex-basis в процентах на iOS требуют, чтобы у
     родителя была «определённая» высота; у position:fixed;inset:0 iOS считает высоту
     как auto для процентов — и потомок (.km-menu) схлопывался по контенту, футер
     прижимался к низу короткой колонки, а под меню оставалась чёрная пустота до низа
     экрана. inset:0 задаёт высоту напрямую через top/bottom-грани, без процентов —
     iOS вычисляет её корректно, колонка всегда на весь .km-root (= весь экран). */
  position: absolute; inset: 0;
  display: flex; flex-direction: column; min-height: 0;
  /* Нижнего отступа нет: «Настройки» вынесены в отдельный прибитый к низу .km-footer,
     а зазор под последний пункт списка даёт padding-bottom у .km-scroll.
     Сверху с запасом: верхней строки с крестиком больше нет, и первый заголовок не
     должен упираться в «бровь» iPhone. Своего transform у панели НЕТ — она стоит
     на месте, двигается основной экран. */
  padding: calc(var(--safe-top) + 26px) 20px 0;
}

/* Прокручиваемая середина РАСТЁТ и заполняет всё свободное место (flex:1 1 auto):
   именно растущая середина надёжно прижимает футер к низу на всех движках
   (в т.ч. iOS/standalone), где auto-margin в флекс-колонке срабатывает через раз.
   При коротком контенте пустое место остаётся ВНУТРИ прокрутки (снизу под списком),
   а «Настройки» стоят у нижнего края колонки. При длинном — середина прокручивается
   внутри себя (min-height:0), футер остаётся внизу. Никакого margin-top:auto. */
.km-scroll {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden;
  /* padding-bottom = высота прибитого футера (border+паддинги+строка 52px) + safe-area,
     чтобы последний прокручиваемый пункт («Сервер») не прятался под .km-footer. */
  margin: 0 -20px; padding: 0 20px calc(74px + env(safe-area-inset-bottom));
}
.km-scroll::-webkit-scrollbar { width: 0; }

/* Заголовок группы (единственный — «РАЗДЕЛЫ»; блока недавних чатов в меню больше
   нет: чаты живут на главном экране и в панели по названию в шапке) */
.km-grouphead {
  font-size: 11px; letter-spacing: .14em; color: var(--dim);
  font-weight: 600; margin: 6px 2px 12px; text-transform: uppercase;
}

/* Разделы */
.km-sections { display: flex; flex-direction: column; gap: 2px; }
.km-row {
  min-height: 56px; display: flex; align-items: center; gap: 14px;
  padding: 8px 2px; cursor: pointer;
}
/* ПЛАШКА ЗНАЧКА — КРУГ, А НЕ КВАДРАТ СО СКРУГЛЕНИЕМ (просьба владельца: «чтобы
   были в стиле»). Круг здесь не просто вкус: в приложении уже всё круглое —
   кружок меню в шапке (44), кнопки шапки, шестерёнка внизу этой же панели
   (.km-gear, 40 и border-radius 50 %), крестик окна. Квадратные плашки строк
   были единственным местом с другой формой.
   ЗАОДНО СОШЛАСЬ ПОДСВЕТКА НАЖАТИЯ. Отклик рисует под значком КРУГ 40 точек
   (.kr-veil.kr-halo, число взято с кнопок шапки) серединой на середину значка.
   Против квадрата 34 со скруглением 10 он ложился «мимо формы»: круглая заливка
   на квадратной плашке. Теперь это два концентрических круга — 34 плашка и 40
   подсветка, ровное кольцо в 3 точки по кругу. Размеры не трогали ни у плашки,
   ни у подсветки: совпадение получилось само, как только форма стала общей. */
.km-chip {
  flex: none; width: 34px; height: 34px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
}
/* Панель теперь узкая (три четверти экрана), а не на всю ширину: строка обязана
   складываться без вылета за край. Название раздела не жмём (это цель нажатия, оно
   должно читаться целиком), а правый показатель сжимаем и обрезаем многоточием — на
   390 всё помещается как было, на 320 сокращается показатель, а не пункт меню. */
.km-row .km-label {
  flex: 1 0 auto; min-width: 0; font-size: 16px; color: var(--bright);
  letter-spacing: -.01em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.km-meta {
  font-size: 13px; color: var(--dim); flex: 0 1 auto; min-width: 0; text-align: right;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.km-meta.ok { color: var(--green); }
.km-meta.accent { color: var(--orange); }
/* Совсем узкий экран (iPhone SE 1-го поколения, 320): показатель в три четверти
   ширины панели не влезает и обрезается в бессмысленное «нет з…». Лучше убрать его
   совсем — значение всё равно видно внутри самого раздела. На 390 и шире (телефон
   Дмитрия) показатель на месте и не обрезается: замер даёт 274 из доступных 276. */
@media (max-width: 360px) {
  .km-meta { display: none; }
}

/* Цвета плашек */
.km-c-files  { background: rgba(138,160,240,.14); }
.km-c-term   { background: rgba(242,242,244,.08); }
.km-c-limit  { background: rgba(138,160,240,.14); }
.km-c-perm   { background: rgba(139,139,147,.16); }
.km-c-server { background: rgba(74,222,128,.14); }
.km-c-dict   { background: rgba(138,160,240,.14); }
/* Почта — ТА ЖЕ подложка, что у файлов, расхода и словаря. Своего цвета ей не
   заводим: новый оттенок в ряду из четырёх строк читался бы как «эта строка
   другого сорта», а она такой же раздел, как соседи. */
.km-c-mail   { background: rgba(138,160,240,.14); }
/* Архив — та же подложка по той же причине: это такой же раздел, как соседи. */
.km-c-arhiv  { background: rgba(138,160,240,.14); }

/* Футер «Настройки» ЖЁСТКО прибит к низу ЭКРАНА: position:absolute;bottom:0
   относительно .km-root (fixed;inset:0, БЕЗ transform). Не зависит от того, как iOS
   посчитал высоту флекс-колонки .km-menu, — геометрически надёжно на всех движках.
   Фон закрывает прокручиваемый под ним список, сверху линия-разделитель, снизу
   safe-area (не залезаем под системную полосу iPhone). Горизонтальный отступ 20px
   совпадает с паддингом .km-menu, чтобы иконка/текст встали вровень со строками. */
/* Линия-разделитель над «Настройками» — ЕЛЕ РАЗЛИЧИМАЯ: цвет обводки (--border)
   читался в меню слишком заметно и резал панель пополам. Берём подложку карточек
   (--panel): на фоне --bg это почти незаметный шов, который лишь отделяет нижний
   блок от списка разделов. Новых цветов не вводим. */
.km-footer {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  background: var(--bg); border-top: 1px solid var(--panel);
  padding: 8px 20px max(12px, env(safe-area-inset-bottom));
}
.km-settings {
  min-height: 52px; display: flex; align-items: center; gap: 14px;
  padding: 6px 2px; cursor: pointer;
}
.km-gear {
  flex: none; width: 40px; height: 40px; border-radius: 50%;
  background: var(--panel); border: 1px solid var(--border);
  display: flex; align-items: center; justify-content: center;
}
.km-settings .km-label { font-size: 16px; color: var(--bright); }

/* ══════════════ ЖИДКОЕ СТЕКЛО — ВИД ПРИЛОЖЕНИЯ ══════════════
   В Офисе вид ОДИН и действует безусловно (§4 плана). В старом приложении эта
   ветка сидела под признаком `data-look="new"` на корне страницы, а рядом жил
   классический двойник; признак снят при переносе, потому что «Стекло» и так
   было видом по умолчанию, а второй вид ветвил не оформление, а поведение
   ленты — печать по буквам, всплытие узлов, прилипание к низу.
   Правила классического вида, записанные через `:not(...)`, при снятии признака
   совпасть уже не могут и удалены: их было одно (`--pop-radius` у оверлея
   записи). Прочие правила ниже переписаны механически — снят только префикс.

   Что здесь и почему именно так:

   1) ПЕРЕМЕННЫЕ. Плоские подложка карточек (--panel) и обводка (--border)
      заменены ПОЛУПРОЗРАЧНЫМИ. Отсюда сразу две просьбы владельца: сквозь
      карточки и пузыри видна общая подложка («лёгкость»), а каждая рамка из
      явной линии превращается в еле заметную кромку — сетка из обводок на
      тёмном фоне пропадает сама, без правки полусотни отдельных правил.

   2) ПОДЛОЖКА. Мягкий переход цвета плюс еле заметное свечение сверху вместо
      плоского почти-чёрного. Именно переходом, а не картинкой и не узором:
      рисуется один раз, при прокрутке не стоит ничего, и текст поверх него
      читается так же, как поверх прежнего фона (перепад яркости небольшой).

   3) СТЕКЛО (размытие фона) — ТОЛЬКО на том, что ПЛАВАЕТ поверх содержимого:
      шапка, капсула ввода, боковая панель, окна подтверждения, меню действий,
      оверлей записи. Разбор образцов владельца дал ровно это правило: стекло
      имеет смысл там, где под ним что-то есть.

   4) В ЛЕНТЕ ЧАТА РАЗМЫТИЯ НЕТ НИ У ОДНОГО ПУЗЫРЯ. Жёстко. Размывать фон под
      каждым пузырём при прокрутке — самое дорогое, что бывает на iPhone: слой
      пересчитывается каждый кадр и на каждом пузыре. Пузыри просто
      полупрозрачные — сквозь них видна подложка, а прокрутка остаётся такой же
      быстрой, как до нового вида (замерено).

   5) УПРУГОСТЬ. Нажатие проседает, отпускание возвращается с лёгким перелётом
      (--spring), панели появляются с пружиной. Характер ОДИН — мягкий: выбор
      «мягко/упруго» из настроек убран по прямой просьбе владельца («оставь одно,
      больше выбора не давай»), вместе с ним снята и ветка data-spring.
      Двигаем ТОЛЬКО прозрачность и transform: это единственные две вещи, которые
      телефон умеет анимировать, не пересчитывая раскладку.
      Вибро-отдачи в веб-приложении на iPhone не существует (Apple её не отдаёт),
      поэтому упругость — единственный доступный «живой» отклик на палец. */
:root {
  /* Подложки и линии — полупрозрачные, поэтому «толщина» вида зависит от одной
     цифры, а не от полусотни правил. */
  --panel: rgba(255, 255, 255, .052);
  --border: rgba(255, 255, 255, .075);
  --accent-soft: rgba(138, 160, 240, .18);
  /* Кромка стекла — ТОНКАЯ. Была .14, потом .10, теперь .055: владелец просил
     «ещё тоньше» уже во второй раз. У настоящего стекла контура нет вовсе, край
     выдаёт блик на ребре (см. --glass-bevel ниже). Поэтому кромку почти гасим, а
     форму держит блик. */
  --glass-edge: rgba(255, 255, 255, .055);
  /* ЗАЛИВКА СТЕКЛА — СВЕТЛАЯ, А НЕ ТЁМНАЯ.
     Это и есть разница между «телеграмным» и «эппловским» стеклом. Телеграмное —
     тёмная полупрозрачная подложка с размытием: оно ЗАТЕМНЯЕТ то, что под ним, и
     выглядит скромным пятном. Эппловское — светлое: оно слегка ОСВЕТЛЯЕТ картинку
     под собой и делает её цвета сочнее, будто подсвечено изнутри. Поэтому заливка
     теперь белая полупрозрачная, а не тёмная; сверху плотнее, снизу прозрачнее —
     свет падает сверху, и у островка появляется объём. */
  /* ЖИДКОЕ СТЕКЛО: БЛИК СВЕРХУ СОБРАН К САМОМУ КРАЮ, А НЕ РАЗМАЗАН ПО ВСЕЙ
     ПОВЕРХНОСТИ. Было два стопа (.155 → .055) — ровный скат, который читается как
     крашеная плашка. У Apple (Liquid Glass, 2025) свет в стекле собирается у
     верхней кромки и быстро гаснет вглубь: это и есть «подсвечено изнутри».
     Поэтому стопа три: у самого края ярче прежнего (.20), к пятой части высоты
     уже почти прежняя плотность (.125), дальше как было (.055). Средняя светлота
     поверхности при этом почти не изменилась — плотность, которую владелец
     принимал у шапки и капсулы ввода, осталась та же, переехал только свет. */
  --glass-fill: linear-gradient(180deg, rgba(255, 255, 255, .20) 0%,
                                        rgba(255, 255, 255, .125) 22%,
                                        rgba(255, 255, 255, .055) 100%);
  /* Более плотная заливка. Раньше её носили островки шапки — владелец сравнил их
     с капсулой ввода и попросил свести верх к низу, поэтому шапка теперь на
     --glass-fill (см. раздел «островки шапки»), а эта заливка осталась там, где
     под поверхностью едет текст, а своего размытия нет: пилюля «Работаю…».
     Разница с --glass-fill ровно та самая «пятая часть», о которой шла речь. */
  --glass-island: linear-gradient(180deg, rgba(255, 255, 255, .175), rgba(255, 255, 255, .075));
  /* Тёмная основа ПОСВЕТЛЕЕ обычной (--glass-base ниже). Для поверхностей, где
     нужна честная читаемость собственных кнопок, но окно должно остаться
     стеклянным: плашка проигрывания голосового. С .72 она стала бы просто тёмной
     панелью, с нулём — тем, на что владелец жаловался (кнопки тонут в тексте). */
  --glass-base-soft: rgba(16, 16, 22, .5);
  /* ЧТО СТЕКЛО ДЕЛАЕТ С КАРТИНКОЙ ПОД СОБОЙ: размывает, ПОДНИМАЕТ НАСЫЩЕННОСТЬ и
     ОСВЕТЛЯЕТ. Осветление здесь главное: без него светлая заливка ложится на
     тёмное и даёт серую муть, а не подсвеченное изнутри стекло. Всё это ОДНА
     цепочка фильтров на слой — телефон считает её тем же проходом, что и одно
     размытие, лишнего слоя не появляется. */
  --glass-lift: blur(var(--glass-blur)) saturate(1.8) brightness(1.22);
  /* ТОЛЩИНА. Светлый блик по верхней грани и тень по нижней — отблеск на ребре
     настоящего стекла. Ровно эти две линии превращают плоское пятно в предмет,
     у которого есть толщина. Блик идёт ПЕРВЫМ: внутренние тени накладываются в
     порядке записи, и верхняя грань обязана быть поверх кромки.
     Обе линии приглушены заодно с кромкой (было .38/.24): при тонкой кромке
     прежний блик сам начинал читаться нарисованным контуром.
     ВНИМАНИЕ: комментарий выше закрывался ДВАЖДЫ — из-за лишнего «конца
     примечания» посреди текста браузер терял всю строку --glass-bevel, и вместе
     с ней ПРОПАДАЛИ все тени островков (правило box-shadow с потерянной
     переменной целиком считается негодным). Держим комментарий одним куском.
     ПЛОСКО ПО ПРОСЬБЕ ВЛАДЕЛЬЦА. Когда сломанный комментарий починили и грани
     наконец заработали, владелец увидел их впервые и сказал: «получились
     выпуклые, 3D-шные, а можно плоскими». Можно: стекло держится не на объёме,
     а на том, что сквозь него видно (прозрачность + осветление + насыщенность
     + размытие). Объём — отдельный слой, снят целиком. Границу островка держит
     волосок --glass-edge: без него на тёмном фоне край растворяется и непонятно,
     где кончается кнопка.

     ВЕРНУЛИ, НО ДРУГИМ (просьба владельца: «эффект стекла обязательно должен быть
     жидкого»). Речь о нынешнем стиле Apple — Liquid Glass, показан на WWDC 2025.
     Его узнают по трём вещам: светящаяся кромка, свет, собирающийся у края
     (преломление), и ощущение толщины. Прежний объём владелец отверг за то, что
     он был ВЫПУКЛОЙ КНОПКОЙ, и виновата в этом была одна строка — ТЁМНАЯ линия по
     нижней грани (inset 0 -1px 0 rgba(0,0,0,.18)). Тень снизу + блик сверху = свет
     падает на выпуклый предмет; так рисуют кнопки, а не стекло.
     У настоящего стекла низ не темнеет, а СВЕТИТСЯ: свет проходит насквозь и
     подсвечивает нижнюю кромку изнутри. Отсюда нынешние три строки:
       1) верхняя грань — блик (.26, чуть слабее прежних .30);
       2) нижняя грань — СВЕТЛАЯ, а не тёмная (.10): отражённый свет вместо тени,
          ровно та разница, из-за которой прошлый заход выглядел выпуклым;
       3) мягкое свечение внутрь на 12 точек по всему периметру (.05) — это
          дешёвая замена настоящему преломлению: у Apple край стекла работает
          линзой и подтягивает к себе свет фона. Настоящую линзу в вебе сделать
          нечем (см. ниже), а «свет, собранный у кромки» — можно, и глаз читает
          именно его.
     Все три — ВНУТРЕННИЕ тени в одной переменной, то есть ни одного нового слоя и
     ни одного нового размытия: это рисование по уже существующей поверхности.
     ВНИМАНИЕ: комментарий выше закрывался ДВАЖДЫ — из-за лишнего «конца
     примечания» посреди текста браузер терял всю строку --glass-bevel, и вместе с
     ней ПРОПАДАЛИ все тени островков. Держать комментарий одним куском.
     ЧЕГО В ВЕБЕ НЕ БУДЕТ, сколько ни старайся: настоящего преломления фона у
     кромки (искажать картинку ПОД элементом умеет только карта смещения, а
     backdrop-filter карт не принимает — ни в Safari, ни где-либо ещё), бликов,
     которые ездят от наклона телефона, и слияния-растекания соседних стеклянных
     форм друг в друга. Это разбор возможностей, а не отговорка: три вещи выше
     дают узнаваемое стекло без единого лишнего кадра работы. */
  --glass-bevel:
    inset 0 1px 0 rgba(255, 255, 255, .26),
    inset 0 -1px 0 rgba(255, 255, 255, .10),
    inset 0 0 12px rgba(255, 255, 255, .05);
  /* ОДНА тень на все стеклянные поверхности. Была вразнобой: у кружка меню и
     капсулы кнопок 0 2px 10px/.26, у поля ввода и меню 0 6px 22px/.28, у панели
     записи /.3, а у карточки лимитов и надписи состояния тени не было вовсе.
     Владелец увидел ровно это: «снизу кое-где черта хорошо видна, а кое-где
     нет». Разнобой родился оттого, что каждая поверхность заводила тень у себя.
     Держим одним значением — тогда нижний край читается одинаково везде. */
  --glass-drop: 0 6px 18px rgba(0, 0, 0, .30);
  /* Здесь лежала запасная (закомментированная) строка со СТАРЫМ объёмом —
     тем самым, с тёмной линией снизу. Убрана, чтобы её случайно не вернули:
     живая строка --glass-bevel выше и есть нынешний объём, а чем он отличается
     от старого — расписано в примечании к ней. */
  /* Стекло без размытия (надпись состояния, всплывающие окна и меню): осветлять
     нечего — под ними либо уже размытый экран, либо лента без своего слоя.
     Им светлая заливка кладётся НА тёмную основу, иначе текст поверх поплывёт. */
  --glass-base: rgba(20, 20, 26, .72);
  /* Сила размытия одним числом — чтобы при замере плавности её можно было
     убавить в ОДНОМ месте, а не по всем стеклянным поверхностям.
     Намеренно СЛАБОЕ (было 20): сквозь островок должно быть видно саму картинку
     под ним — проезжающий текст и пузыри узнаются, — а не серая каша. Ровно
     столько, чтобы надпись поверх читалась. Заодно это самая дешёвая настройка
     плавности: одно число на все стеклянные поверхности. */
  --glass-blur: 16px;
  /* Всплывающие списки и меню живут секунды и закрывают собой содержимое —
     им размытие сильнее, чтобы пункты читались поверх чего угодно. */
  --glass-blur-pop: 22px;
  /* Высота шапки-островков. Ею же экран отбивает содержимое сверху, поэтому
     число одно на всё приложение (и на заготовки первого кадра в index.html). */
  --topbar-h: calc(56px + var(--safe-top));
  /* Кривая возврата после нажатия: лёгкий перелёт за конечную точку и осадка.
     Ею пользуется и оформление, и сторожка отклика (js/press.js читает её прямо
     из этой переменной) — кривая возврата в приложении ровно одна. */
  --spring: cubic-bezier(.32, 1.26, .5, 1);
  /* Глубина просадки ЗДЕСЬ БОЛЬШЕ НЕ ЗАДАЁТСЯ: она зависит от выбранной силы
     отклика и от того, крупнее элемент пальца или мельче, — то есть от замера
     самого элемента, которого в стилях нет. Три силы перечислены в MODES
     (js/press.js), там же и порог «крупнее пальца». Прежняя переменная --press
     убрана, чтобы не осталось второго, устаревшего числа. */
  /* ОБЩАЯ ПОДЛОЖКА. Две правки владельца сошлись здесь и тянут в разные стороны,
     поэтому объясняю обе:
     1) ВЕРХ ПРИТЕМНЁН. Раньше подложка светлела кверху, и самый светлый кусок
        экрана оказывался ровно там, где висят стеклянные островки шапки. Стекло
        ОСВЕТЛЯЕТ то, что под ним, — значит на светлом оно пропадает, а на тёмном
        видно. Притемнение верха ДОБАВЛЯЕТ стекла, а не убавляет.
     2) СВЕЧЕНИЕ ОПУЩЕНО В СЕРЕДИНУ, НО ПРИТУШЕНО. Сперва было .24 из точки над
        экраном — переход почти не читался глазом, и на главном (где под
        карточками только фон, а не проезжающая лента) стеклу было нечего
        показывать. Тогда пятно подняли до .34 и опустили на треть высоты —
        туда, где на главном лежат карточка лимитов и плитки проектов. В чате
        это оказалось перебором: владелец сказал «фон сильно просвечивает» ровно
        про середину экрана. Сейчас .22 — пятно на прежнем месте и прежней
        формы, но слабее примерно на треть: подложка под карточками главного
        по-прежнему живая, а середина чата перестала светиться сквозь текст.
     Рисуется по-прежнему одним переходом, при прокрутке не пересчитывается. */
  --look-bg:
    radial-gradient(120% 56% at 50% 27%, rgba(138, 160, 240, .11), rgba(138, 160, 240, 0) 70%),
    linear-gradient(180deg, #0C0C11 0%, #16161F 34%, #101017 68%, #08080B 100%);
}

/* ══════════════ ФОН — ОТДЕЛЬНАЯ НАСТРОЙКА, НЕ ЧАСТЬ ОФОРМЛЕНИЯ ══════════════
   «Тёмный» (признака нет) — то, что выше: почти чёрная подложка с еле заметным
   голубым свечением (.11). «Перламутр» (data-bg="pearl") — та же подложка, но
   свечение вдвое сильнее: голубоватый отлив, каким подложка была раньше.
   ОТКУДА ЧИСЛО: это не выдумка и не подбор на глаз, а ПРЕЖНЕЕ значение из истории
   этого файла — .22 держалось до последней правки (её и описывает примечание
   выше, где ещё написано «сейчас .22»), а до того было .34, и вот .34 владелец
   отверг словами «фон сильно просвечивает». Берём то, что он принимал.
   ПОЧЕМУ ФОН ОТДЕЛЬНОЙ НАСТРОЙКОЙ: в старом приложении он обязан был сочетаться
   с любым оформлением, поэтому подложка кладётся на тело и обёртку своим
   правилом, а блок стоит НИЖЕ ветки стекла — значит при равной силе побеждает он.
   При переносе в Офис это ничего не меняет: вид один, порядок тот же.
   Боковой панели меню отдельного правила не нужно: она берёт var(--look-bg),
   а переменную мы здесь и переопределяем. */
:root[data-bg="pearl"] {
  --look-bg:
    radial-gradient(120% 56% at 50% 27%, rgba(138, 160, 240, .22), rgba(138, 160, 240, 0) 70%),
    linear-gradient(180deg, #0C0C11 0%, #16161F 34%, #101017 68%, #08080B 100%);
}
:root[data-bg="pearl"] body,
:root[data-bg="pearl"] #app-shell { background: var(--look-bg); }
/* Нижний цвет градиента — на сам холст (см. правило html выше). Важно: градиент
   --look-bg применяется и при «Стекле» с тёмным фоном, а не только при перламутре,
   поэтому цвет холста задаём для ОБОИХ случаев — иначе внизу виден шов между
   концом градиента (#08080B) и холстом (#0E0E10). */
:root[data-bg="pearl"], :root { --canvas-bg: #08080B; }

/* ── 2. Подложка ──
   Кладём и на тело, и на обёртку основного экрана. Обёртке непрозрачная своя
   подложка ОБЯЗАТЕЛЬНА (иначе сквозь неё просвечивает боковое меню, см. #app-shell
   выше) — поэтому не «убираем фон, чтобы виден был градиент тела», а даём тот же
   градиент обоим. Тело не прокручивается (прокрутка живёт внутри .screen), значит
   переход рисуется один раз и при листании не пересчитывается. */
body,
#app-shell { background: var(--look-bg); }
/* Боковая панель: та же подложка, но полупрозрачная — сквозь неё виден градиент
   тела. Размытия у неё НЕТ намеренно: под панелью лежит только тот же ровный
   переход цвета, размывать нечего, а вот платить за размытие пришлось бы каждый
   кадр хода открытия (панель едет параллаксом). Кромка — по правому краю,
   внутренней тенью: обычная граница сдвинула бы содержимое на пиксель. */
/* ЖИДКОЕ СТЕКЛО У БОКОВОЙ ПАНЕЛИ — только по ПРАВОМУ краю, и это не лень.
   Панель прижата к левому краю экрана: три её грани за экраном, видна одна —
   правая, та, из-под которой выезжает основной экран. Ей и достаётся всё стекло:
   светящийся волосок по самой кромке (было .05, стало .10) и мягкое свечение
   внутрь на 18 точек — тот же приём «свет собирается у края», что у островков
   (см. --glass-bevel). Общий --glass-bevel сюда не годится: он светит по всему
   периметру, а здесь периметр один.
   Размытия у панели по-прежнему НЕТ (под ней только ровный переход цвета, см.
   ниже) — свечение рисуется поверх заливки и на кадр хода ничего не добавляет. */
.km-root {
  background: linear-gradient(180deg, rgba(28, 28, 36, .72), rgba(14, 14, 18, .72)), var(--look-bg);
  box-shadow: inset -1px 0 0 rgba(255, 255, 255, .10),
              inset -18px 0 26px -18px rgba(255, 255, 255, .07);
}

/* ── 3. Стекло = ОТДЕЛЬНЫЕ ПЛАВАЮЩИЕ ОСТРОВКИ ──
   Главное, что было понято неверно в прошлый заход: стекло — это НЕ «панель с
   размытием». Панель с размытием, под которую ничего не заезжает, размывает
   ровный фон и выглядит просто чуть другим серым — владелец так и сказал:
   «эффекта стекла нет вообще». Стекло появляется только тогда, когда СОДЕРЖИМОЕ
   ПРОЕЗЖАЕТ ПОД поверхностью и просвечивает СКВОЗЬ неё.
   Отсюда вся перестройка ниже:
   1) шапка вынута из потока и не имеет фона — вместо полосы три островка
      (кружок меню, капсула названия, капсула кнопок), между ними голый чат;
   2) экран занимает ВСЮ высоту, а отступы сверху/снизу делаются полями
      ПРОКРУТКИ — не обрезкой. Поэтому первое и последнее сообщение можно
      выкрутить из-под островков, а не «оно там кончается».
   Плата за это — размытый слой перестал быть статичным (под ним теперь едет
   лента). Поэтому сила размытия убавлена (--glass-blur), а площадь стекла
   сведена к самим островкам: сплошных поверхностей во весь экран больше нет. */

/* ── 3а. ВОЛОСОК ПО КОНТУРУ — ОДИНАКОВЫЙ ПО ВСЕМУ ПЕРИМЕТРУ ──
   Владелец: «на всех этих капсулах снизу есть волосок, только он не тонкий,
   а толще, чем везде по кругу». Замер (увеличение x6, яркость по всем четырём
   граням пилюли чата) подтвердил и назвал виновника:
       сверху −2 (линии не видно вовсе), слева +12, справа +11, СНИЗУ +25.
   Толщина везде ровно 1 точка — «толще» это разница ЯРКОСТИ, а не ширины.
   ПРИЧИНА — НЕ вторая обводка, не тень и не скругление. Причина в том, КАК
   браузер кладёт заливку-переход под саму рамку. Переход (--glass-fill и
   родня) светлый вверху и тёмный внизу; по умолчанию картинка заливки
   разложена по ВНУТРЕННЕЙ коробке (background-origin: padding-box), а
   закрашивает она коробку ВМЕСТЕ С РАМКОЙ — и в полоску рамки попадает
   ПОВТОР перехода, то есть его противоположный конец:
     • в полоску СНИЗУ ложится САМОЕ СВЕТЛОЕ начало перехода, а прямо над ней
       заливка в этом месте самая тёмная → линия вспыхивает вдвое ярче;
     • в полоску СВЕРХУ ложится самый тёмный конец, а под ней заливка самая
       светлая → линия пропадает совсем (замер даже минус: она темнее заливки).
   Доказательство от противного было тут же, в этом файле: капсула названия
   в шапке рисует свой контур не рамкой, а внутренней тенью ПОВЕРХ заливки —
   и замер у неё ровный по кругу: +11.7 / +12.3 / +10.7 / +10.4.
   ЛЕЧЕНИЕ — одна строка на каждую стеклянную поверхность: раскладывать переход
   по коробке ВМЕСТЕ С РАМКОЙ (background-origin: border-box). Тогда под рамкой
   лежит ровно тот же оттенок перехода, что и рядом с ней, превышение везде
   одинаковое (+11…+12 по замеру после правки), а волосок остаётся на месте — он
   и нужен, чтобы край островка не растворялся на тёмном.
   Ни размеров, ни раскладки это не трогает: меняется только раскладка КАРТИНКИ
   заливки внутри той же коробки.
   ПОЧЕМУ СТРОКА ПОВТОРЕНА У КАЖДОЙ ПОВЕРХНОСТИ, А НЕ ВЫНЕСЕНА В ОДНО ПРАВИЛО:
   краткая запись `background: …` СБРАСЫВАЕТ эту раскладку обратно. Общее правило
   молча съедалось бы любым правилом заливки, стоящим ниже по файлу, — и волосок
   снизу вернулся бы только у части элементов. Держим рядом с заливкой.
   ВНИМАНИЕ: карточка лимитов и плитки продублированы в заготовках первого кадра
   (index.html) — там та же строка обязана быть, иначе первый кадр мигнёт толстой
   чертой снизу. */

/* Шапка: рамка без фона. Касания сквозь неё проходят к ленте (pointer-events:
   none), ловят их только сами островки — иначе невидимая полоса поверх чата
   съедала бы прокрутку у верхнего края. */
.topbar {
  position: absolute; top: 0; left: 0; right: 0; z-index: 20;
  background: none;
  border-bottom: none;
  pointer-events: none;
}
/* ЗАТЕНЕНИЕ ПОД ШАПКОЙ. Не полоса и не фон шапки: мягкий переход в прозрачность
   поверх того, что проезжает под островками (лента чата, карточки главного).
   Зачем: стекло осветляет картинку под собой, поэтому на светлом оно пропадает.
   Пока под шапку заезжает светлый пузырь или карточка, островки теряются — это и
   есть жалоба владельца. Тёмная подкладка возвращает им контраст в любой момент
   прокрутки, а не только на пустом фоне.
   z-index: -1 внутри собственного слоя шапки — рисуется НАД содержимым экрана и
   ПОД самими островками. Касаний не ловит (шапка их и так пропускает). */
.topbar::before {
  content: ''; position: absolute; left: 0; right: 0; top: 0; z-index: -1;
  height: calc(var(--topbar-h) + 24px);
  background: linear-gradient(180deg, rgba(0, 0, 0, .5), rgba(0, 0, 0, 0));
  pointer-events: none;
}
/* Экран занимает всю высоту обёртки (шапка вышла из потока), а содержимое
   отбивается сверху ПОЛЕМ — оно прокручиваемое, поэтому верхняя карточка
   свободно выезжает из-под островков. */
.screen { padding-top: calc(var(--topbar-h) + 16px); }
/* Касания возвращаем всем содержимым шапки — ловят их сами островки. */
.topbar > * { pointer-events: auto; }
/* …но НЕ при открытом боковом меню: уехавший экран целиком неактивен (правило
   body.km-on #app-shell > * выше), и островки шапки обязаны подчиняться тому же —
   иначе тап по уехавшей кнопке меню срабатывал бы вместо закрытия меню. */
body.km-on .topbar > *,
body.km-closing .topbar > * { pointer-events: none; }
/* Островки шапки. Заливка — РОВНО ТА ЖЕ, что у капсулы ввода (--glass-fill).
   Раньше она была плотнее примерно на пятую часть (--glass-island): считалось,
   что глиф на кнопке обязан читаться, что бы ни проезжало под стеклом. Владелец
   сравнил верх и низ на телефоне и выбрал нижнее — верх сведён к нему.
   Плату за это знаем и приняли: на длинном пёстром тексте название чата может
   местами теряться. Страховка — затенение под шапкой (.topbar::before выше):
   контраст островкам держит оно, а не собственная плотность заливки.
   Размытие+осветление — только у тех трёх, у кого есть своя заливка: фильтр без
   заливки даёт мутный прямоугольник с резкими краями, а не стекло. */
.burger,
.topbar-caps,
.topbar .title {
  -webkit-backdrop-filter: var(--glass-lift);
  backdrop-filter: var(--glass-lift);
}
/* Кружок меню и капсула кнопок: заливка + блик по верхней грани + мягкая тень
   под островком (та самая «толщина»).
   ЦВЕТ ОБВОДКИ КРУЖКА МЕНЮ ЗДЕСЬ НЕ ЗАДАЁТСЯ — и это не мелочь. На этой обводке
   сидит ЖИВОЙ ИНДИКАТОР СВЯЗИ с Маком (.burger[data-link=…]). Прошлый заход
   задавал цвет тут — и правило вида оказалось СИЛЬНЕЕ правила состояния:
   «нет связи» переставало быть красным. Замер цвета обводки это подтвердил:
   при data-link="offline" выходило rgba(255,255,255,.14) вместо красного.
   Теперь цвет ободка целиком за правилами состояния, а «на связи» берёт --border
   — он в новом виде и без того почти прозрачный, то есть контур тоньше на вид
   (как и просили). Толщину (2px) не трогаем: на ней держится сам индикатор. */
.burger,
.topbar-caps {
  background: var(--glass-fill);
  background-origin: border-box;   /* ровный волосок по кругу — см. раздел 3а */
  box-shadow: var(--glass-bevel), var(--glass-drop);
}
/* Капсула кнопок: обводка тоньше (2 → 1) и приглушённее, а внутренняя геометрия
   сохранена полем в 1px — поле каждой кнопки остаётся 40×40 (44 − 1 − 1 − 1 − 1),
   ширина капсулы прежняя. Иначе кнопки внутри стали бы овалами 40×42. */
.topbar-caps {
  border-width: 1px; padding: 1px;
  border-color: var(--glass-edge);
}
/* Название чата — капсула, а не просто текст: на стекле надпись без подложки
   тонет в проезжающем тексте. Высота ЖЁСТКО 44 — ровно как у кружка меню и у
   капсулы кнопок: три островка шапки обязаны быть одной высоты и стоять на одной
   линии (грид шапки центрирует их сам). Раньше высота набиралась полями по
   тексту и выходила 36 против 44 — островки были разного роста.
   Обводка — внутренней тенью, чтобы не менять размеры (у названия жёсткая
   колонка грида). */
.topbar .title {
  height: 44px; padding: 0 16px; border-radius: 999px;
  background: var(--glass-fill);
  box-shadow: var(--glass-bevel), inset 0 0 0 1px var(--glass-edge),
              var(--glass-drop);
}
/* «ГДЕ-ТО ЕСТЬ НЕПРОЧИТАННОЕ» — еле заметная обводка капсулы названия.
   Владелец: «саму обводку только, чтобы не бросалась в глаза».
   Почему именно здесь: эта капсула есть на КАЖДОМ экране (на главном подписана
   «Чаты», в диалоге — именем чата) и по нажатию открывает список всех чатов —
   то есть намёк стоит ровно на той двери, за которой видно, куда пришло.
   Обводка внутренняя, размеры не меняет. Кладём поверх обычной кромки: та же
   тень с тем же смещением, но зелёная и приглушённая. */
/* Кому системно неприятно движение — подложка остаётся, блик не бежит.
   Признак «есть непрочитанное» при этом не теряется. */
@media (prefers-reduced-motion: reduce) {
  :root[data-unread] .topbar .title::before { animation: none; opacity: 0; }
  :root[data-unread] .topbar .title,
  :root[data-unread] .topbar .title {
    background-color: rgba(52, 199, 89, .2);
  }
}
/* ══════════ «ЕСТЬ НЕПРОЧИТАННОЕ» — ТОЧКА БЕЖИТ ПО КРАЮ КАПСУЛЫ ══════════
   Владелец: «просто точка бегать должна, и всё, этого достаточно».
   Прошлая попытка рисовала светящийся ОБОДОК и делала это вырезанием середины
   маской — на скруглённой капсуле такой вырез даёт КВАДРАТНЫЕ углы (маска не
   наследует скругление). Здесь ободка нет вовсе: по краю едет один маленький
   кружок, а форма плашки не трогается ничем.
   Путь считается по геометрии капсулы: высота 44, скругление 22, кружок идёт по
   средней линии на 3 точки наружу. Прямые участки — сверху и снизу, закругления
   — пятью кадрами по дуге (−90°, −45°, 0°, 45°, 90°), поэтому на углах точка
   идёт по дуге, а не срезает их.
   Проценты по горизонтали — чтобы путь сам подстраивался под ширину названия. */
@keyframes unread-dot {
  /* Время роздано ПОПОЛАМ между прямыми и закруглениями — по четверти круга на
     каждый участок. Прошлая раскладка давала прямым 22% пути, а дугам 3%, и
     точка на углах дёргалась. Внутри дуги четыре равных шага по 6,25% —
     скорость держится ровной. */
  0%     { left: 22px;               top: -3px; }     /* верх, слева направо */
  25%    { left: calc(100% - 22px);  top: -3px; }
  31.25% { left: calc(100% - 4.3px); top: 4.3px; }    /* правая дуга */
  37.5%  { left: calc(100% + 3px);   top: 22px; }
  43.75% { left: calc(100% - 4.3px); top: 39.7px; }
  50%    { left: calc(100% - 22px);  top: 47px; }     /* низ, справа налево */
  75%    { left: 22px;               top: 47px; }
  81.25% { left: 4.3px;              top: 39.7px; }   /* левая дуга */
  87.5%  { left: -3px;               top: 22px; }
  93.75% { left: 4.3px;              top: 4.3px; }
  100%   { left: 22px;               top: -3px; }
}
:root[data-unread] .topbar .title {
  position: relative;
  border-radius: 999px;
  /* Заливка плашки — как была: пока точка на дальней стороне, признак виден. */
  background-color: rgba(52, 199, 89, .16);
}
:root[data-unread] .topbar .title::before {
  content: '';
  position: absolute;
  width: 7px;
  height: 7px;
  margin: -3.5px;                    /* центр кружка ровно на пути */
  border-radius: 50%;
  pointer-events: none;
  z-index: 2;                        /* поверх названия: кружок мелкий, букв не закроет */
  background: #EAFFF2;
  box-shadow: 0 0 6px 2px rgba(120, 255, 170, .9),
              0 0 14px 5px rgba(52, 199, 89, .45);
  animation: unread-dot 7s linear infinite;
}
/* РОВНАЯ СКОРОСТЬ — только так, а не ключевыми кадрами.
   Кадрами скорость выровнять НЕЛЬЗЯ: доли времени приходится задавать жёстко
   (четверть на верх, четверть на дугу), а реальная длина этих участков зависит
   от ширины плашки — она меняется с названием чата. На широком названии верх
   длинный, и точка по нему летит, а по короткой дуге ползёт. Владелец это и
   видел.
   Здесь путь задан как САМА ФОРМА капсулы, а расстояние вдоль него телефон
   считает сам: равные доли времени = равные доли ПУТИ, то есть постоянная
   скорость на любой ширине.
   Оставляем и старые кадры: если телефон движения по контуру не умеет, точка
   поедет по ним — неровно, но поедет. */
@supports (offset-path: inset(0 round 999px)) {
  @keyframes unread-dot-path { to { offset-distance: 100%; } }
  :root[data-unread] .topbar .title::before {
    left: 0; top: 0; margin: 0;
    offset-path: inset(-3px round 999px);
    offset-distance: 0%;
    offset-rotate: 0deg;
    animation: unread-dot-path 7s linear infinite;
  }
}
/* Кому системно неприятно движение — кружок стоит на месте, у левого края. */
@media (prefers-reduced-motion: reduce) {
  :root[data-unread] .topbar .title::before { animation: none; left: 22px; top: -3px; }
}

/* ГАЛОЧКА У НАЗВАНИЯ УБРАНА (просьба владельца). Убран ТОЛЬКО значок: сама
   капсула названия остаётся кнопкой (.title.has-drop, обработчик в app.js) и
   по-прежнему открывает панель всех открытых чатов. Прячем стилями, а не в коде,
   чтобы выключатель «Новый вид» честно возвращал прежний вид с галочкой. */
.topbar .title .title-chev { display: none; }
/* Капсула ввода внизу чата — главный островок: лента проезжает ПОД ней и видна
   насквозь (см. правила экрана чата ниже). */
.term-input-row {
  background: var(--glass-fill);
  background-origin: border-box;   /* ровный волосок по кругу — см. раздел 3а */
  border-color: var(--glass-edge);
  /* ТОЛЬКО РАЗМЫТИЕ, БЕЗ ОСВЕТЛЕНИЯ — 11 августа 2026, решение владельца.
     Одно и то же стекло на разной площади ощущается по-разному: наверху три
     маленьких островка, внизу один большой блок, и общая цепочка --glass-lift
     (размытие + насыщенность + осветление) читалась внизу молочным светящимся
     пятном. Осветление и насыщенность здесь сняты, размытие осталось — и берётся
     из той же переменной силы --glass-blur, чтобы при будущей правке силы
     размытия капсула не отстала от остальных поверхностей.
     ВЕРХНИЕ ОСТРОВКИ — ЭТАЛОН, они владельцу нравятся и не тронуты: --glass-lift
     как была, так и остаётся у шапки, плашки голосового и строки поиска.
     Не «приводить к единообразию» обратно: верх и низ разведены сознательно. */
  -webkit-backdrop-filter: blur(var(--glass-blur));
  backdrop-filter: blur(var(--glass-blur));
  box-shadow: var(--glass-bevel), var(--glass-drop);
}
/* ПЛАШКА ФАЙЛА В ПОЛОСЕ ВЛОЖЕНИЙ — ПЛОТНЕЕ ОКРУЖАЮЩЕГО СТЕКЛА, а не прозрачнее.
   Своих правил вида у неё не было: она брала общую подложку карточек (--panel), а
   та в этом облике почти прозрачна (.052 белого). Пока полоса стояла ОТДЕЛЬНЫМ
   блоком над капсулой, это ещё сходило: под плашкой была лента. Теперь плашка
   лежит ВНУТРИ капсулы, то есть поверх её собственной светлой заливки, — .052
   поверх .20 не видно вовсе, и от плашки оставался бы висящий в воздухе значок.
   Лечение то же, что у плашки проигрывания голосового (.read-bar ниже): плотная
   светлая заливка поверх приглушённой тёмной основы. Своего размытия НЕ даём —
   размывать нечего, полоса лежит на уже размытом стекле капсулы, а лишний слой
   размытия на iPhone стоит кадров.
   Кромка тоже своя: --glass-edge (.055) внутри капсулы неразличима, поэтому у
   плашки она вдвое ярче — иначе не видно, где кончается одна и начинается
   соседняя. */
.term-input-row .photo-thumb.photo-file {
  background: var(--glass-island), var(--glass-base-soft);
  border-color: rgba(255, 255, 255, .12);
}

/* ── 3б. Экран чата: лента от верхнего края экрана до нижнего ──
   Было: лента — обычный сосед в колонке, поэтому она ОБРЕЗАЛАСЬ по нижней кромке
   шапки и по верхней кромке капсулы ввода. Под островки не заезжало ничего,
   размывать было нечего — отсюда «стекла не видно».
   Стало: лента выведена из потока и занимает ВЕСЬ экран (сверху донизу), а
   островки лежат поверх неё. Отступы под шапку и под капсулу сделаны ПОЛЯМИ
   ПРОКРУТКИ, а не обрезкой: первое и последнее сообщение выкручиваются из-под
   островков пальцем, ничего не «кончается» под ними навсегда.
   РАСКЛАДКУ КЛАВИАТУРЫ ЭТО НЕ ТРОГАЕТ: тело по-прежнему садится на видимое окно
   (--vv-top/--vv-height), экран по-прежнему занимает всю его высоту, капсула
   по-прежнему последняя в потоке и прижата к низу. Меняется только то, КТО
   занимает свободное место: раньше лента распирала колонку, теперь она лежит
   подложкой, а свободное место забирает выравнивание в конец. */
.screen.screen-chat {
  padding-top: 0;
  justify-content: flex-end;
}
/* КАПСУЛА ВВОДА НИЖЕ. Владелец: «панель с полем ввода и голосовым сейчас
   высоко». Общее правило держит её на safe-area + 16; убираем ровно эти 16 до
   4 — капсула опускается на 12 точек.
   БЕЗОПАСНУЮ ЗОНУ НЕ ТРОГАЕМ: env(safe-area-inset-bottom) остаётся в формуле,
   иначе нижний ряд кнопок уехал бы под системную полоску жеста «домой».
   Второе правило (клавиатура открыта) выглядит лишним, но обязательно: селектор
   с признаком вида СИЛЬНЕЕ общего правила body.kb-open, и без этой строки
   зазор над клавиатурой считался бы по формуле выше. Повторяем прежние 6 точек
   слово в слово — раскладка с клавиатурой не меняется ни на пиксель. */
.screen.screen-chat > .term-input-row {
  /* Владелец подбирал высоту капсулы в два приёма (01.08.2026, после того как
     низ экрана наконец стал доступен): было «безопасное поле + 4» = 38 точек,
     опустили до 22, затем подняли на 4 — итог «безопасное поле − 8» = 26.
     Ниже не опускаем: под капсулой проходит системная чёрточка домашнего
     экрана, и её нельзя перекрывать кнопками. */
  margin-bottom: calc(env(safe-area-inset-bottom) - 8px);
}
body.kb-open .screen.screen-chat > .term-input-row {
  margin-bottom: 6px;
}
.screen.screen-chat > .snapshot {
  position: absolute; left: 0; right: 0; top: 0; bottom: 0;
  margin: 0; z-index: 0;
  /* Сверху — высота шапки-островков плюс воздух; снизу — высота нижней стопки
     (капсула ввода со всем, что над ней). Высота стопки меняется (миниатюры
     фото, очередь, плеер), поэтому её измеряет chats.js и кладёт в --dock-h;
     запасное число на случай, если измерение ещё не случилось.
     БОКОВОЕ ПОЛЕ — 16, ПО ВНЕШНЕМУ КРАЮ КАПСУЛЫ ВВОДА. Поле держит ЛЕНТА, одно
     на всё содержимое: и текст ответа, и пузыри, и карточки файлов, и черта, и
     бегущие точки «работаю» встают на одну линию с обеих сторон.
     ЗАМЕР НА 393 (окно шириной 393 точки):
       было  — текст ответа 14 слева и 14 справа, капсула ввода 16 и 16:
               длинные строки ответа выезжали за капсулу на 2 точки с каждой
               стороны, и владелец видел это как «текст лезет за края»;
       стало — 16 и 16 у обоих: линия текста и внешний край капсулы совпадают.
     Откуда берётся 16 у капсулы: она стоит в потоке экрана, а у экрана боковое
     поле 16 (.screen). Двигали ТОЛЬКО ленту — ни капсулу, ни кнопки, ни островки
     шапки не трогали.
     Свои боковые поля у пузыря ответа, дневника действий и строки состояния
     сняты ниже — иначе они сложились бы с этим.
     ДОБАВКА ПОСЛЕ ЖИВОЙ ПРОВЕРКИ: владелец посмотрел равнение по капсуле и
     попросил «ещё пару точек». Стало 22 — текст теперь стоит на 6 точек ВНУТРИ
     от края капсулы, воздуха с боков заметно больше, строка короче. Полное
     равнение по НАДПИСИ внутри капсулы (это было бы 40) владелец отверг сам:
     «сильно сдвинешь — будет выглядеть не очень». 22 — середина между
     равнением по краю капсулы и по её надписи. Следующая ступень, если
     попросит ещё, — 26; дальше начинается тот самый перебор. */
  padding: calc(var(--topbar-h) + 8px) 22px calc(var(--dock-h, 150px) + 8px);
}
/* Нижняя стопка — поверх ленты. position:relative нужен только ради слоя.
   Полосы вложений здесь нет намеренно: она лежит внутри капсулы и едет наверх
   вместе с ней, своего слоя ей не нужно. */
.screen.screen-chat > .qbar,
.screen.screen-chat > .chat-status-row,
.screen.screen-chat > .read-bar,
.screen.screen-chat > .queue-bar,
.screen.screen-chat > .term-input-row {
  position: relative; z-index: 2;
}
/* ПЛАШКА ПРОИГРЫВАНИЯ ГОЛОСОВОГО — единственная поверхность, которую владелец
   просил сделать ПЛОТНЕЕ, а не прозрачнее. Своих правил вида у неё не было вовсе:
   она брала общую подложку карточек (--panel), а та в новом виде почти прозрачна.
   Итог на телефоне — сквозь плашку насквозь читалась переписка, а её собственные
   кнопки (пауза, дорожка, скорость, стоп) в этом тексте тонули.
   Лечение из трёх слоёв: своё размытие (лента едет прямо под ней), плотная
   светлая заливка --glass-island и приглушённая тёмная основа --glass-base-soft.
   Основа намеренно НЕ --glass-base (.72): с ней плашка стала бы просто тёмной
   панелью. С .5 сквозь неё виден цвет и движение ленты — стекло осталось стеклом,
   а кнопки читаются поверх любого текста. */
.read-bar {
  background: var(--glass-island), var(--glass-base-soft);
  background-origin: border-box;   /* ровный волосок по кругу — см. раздел 3а */
  border-color: var(--glass-edge);
  -webkit-backdrop-filter: var(--glass-lift);
  backdrop-filter: var(--glass-lift);
  box-shadow: var(--glass-bevel), var(--glass-drop);
}
/* Круглый «стоп» справа: на плотном стекле его бледная кромка терялась. */
.read-stop { border-color: var(--glass-edge);
  background: rgba(255, 255, 255, .06); }
.read-rate { border-color: var(--glass-edge);
  background: rgba(255, 255, 255, .06); }
/* Строка поиска по чату выезжает из-под шапки отдельным островком и тоже
   пропускает ленту под собой (в потоке она встала бы над капсулой ввода —
   вся стопка теперь выровнена в конец). */
.screen.screen-chat > .find-wrap {
  position: absolute; left: 0; right: 0; top: calc(var(--topbar-h) + 6px);
  z-index: 12;
}
.find-bar {
  background: var(--glass-fill); border-color: var(--glass-edge);
  background-origin: border-box;   /* ровный волосок по кругу — см. раздел 3а */
  -webkit-backdrop-filter: var(--glass-lift);
  backdrop-filter: var(--glass-lift);
  box-shadow: var(--glass-bevel), var(--glass-drop);
}
/* ── СОСТОЯНИЕ — ПОСЛЕДНЕЙ СТРОКОЙ САМОЙ ПЕРЕПИСКИ ──
   Владелец: «убери плашку там, где над полем ввода расшифровываю, бегают точки.
   Это всё должно быть в поле чатов, а не на отдельных капсулах».
   Было: отдельная пилюля со своей заливкой и тенью, стоявшая в нижней стопке над
   капсулой ввода. Стало: та же надпись живёт ВНУТРИ ленты, последней строкой
   разговора, — там же, где идёт сам разговор. Своей подложки у неё больше нет
   вовсе: она стоит в потоке сообщений, а не поверх них, топить ей нечего.
   Переехали ВСЕ состояния, а не только два названных: бегущие точки «идёт ход»,
   «ждёт ответа», «расшифровываю…», «отправляю» при выборе варианта, предел на
   число фотографий, «доставлено» (зелёным) и «не доставлено» (красным).
   Узел один и тот же — переехал его дом, а не он сам, поэтому ни одна веточка
   кода, которая пишет в эту надпись, не тронута.
   Своего бокового поля у строки нет: линию 16 держит сама лента (см. её padding
   выше), и строка состояния встаёт на ту же левую линию, что и текст разговора. */
/* ВЫСОТА СТРОКИ ЗАКРЕПЛЕНА — И ТЕПЕРЬ ЭТО ВАЖНЕЕ, ЧЕМ БЫЛО.
   Надпись появляется и исчезает по несколько раз за ход. Раньше она стояла в
   нижней стопке, и незакреплённая высота дёргала стопку. Теперь она стоит В
   ЛЕНТЕ, и цена ошибки выше: изменись высота — изменится длина всей ленты, а
   браузер держит не «низ», а число прокрутки, и весь разговор поехал бы вверх-
   вниз на каждое появление точек. Поэтому строка занимает свои 22+8 ВСЕГДА,
   пустая в том числе, и лента не меняет длину ни на точку.
   22 + 8 — ровно столько же, сколько занимала прежняя строка в стопке (её
   высота 22 плюс отбивка 8). Поэтому расстояние «последнее сообщение — поле
   ввода» осталось прежним, принятым владельцем: сколько ушло из стопки, столько
   же пришло в ленту. */
.snapshot > .chat-status {
  /* Перенос разрешён намеренно: коротким состояниям (точки, «расшифровываю…»)
     он не нужен, а длинный текст ошибки от моста иначе вылез бы за край. */
  display: flex; align-items: center; flex-wrap: wrap;
  min-height: 22px; margin: 0 0 8px; padding: 0;
  background: none; box-shadow: none; border-radius: 0;
}
/* Прежнее место в нижней стопке пустует — убираем его целиком (вместе с ним из
   стопки уходят те самые 22+8, которые надпись унесла с собой в ленту).
   Правило только для НОВОГО вида: при выключенном выключателе строка остаётся на
   прежнем месте над полем ввода и ведёт себя ровно как до переделки. */
.screen.screen-chat > .chat-status-row { display: none; }
/* Середина окна просмотра файла: тёмная площадка под картинкой/страницей
   осталась, но кромки внутри окна теперь ровно те же, что у остальных окон. */
.viewer-body { background: rgba(0, 0, 0, .35); }
.viewer-bar { border-top-color: var(--glass-edge); }
/* ── ЗАПИСЬ ГОЛОСА: НИ СВОЕЙ ПОВЕРХНОСТИ, НИ СВОЕГО МЕСТА ──
   Здесь стояли стекло панели записи и её ход появления, потом — общая с окнами
   поверхность и общий ход, потом ветка «панель над клавиатурой». Не осталось
   ничего: окна записи нет вовсе, запись живёт слоем внутри капсулы ввода
   (.rec-live выше), а капсула про клавиатуру знает и без нас. */
/* ── КАРТОЧКА ВАРИАНТОВ ОТВЕТА — ТА ЖЕ ПАНЕЛЬ, ЧТО ОКНА И МЕНЮ ──
   Строки в ней общие (.win-item), здесь только поверхность под ними: та же
   форма (--pop-radius), та же кромка, тот же блик и тень.
   РАЗМЫТИЯ ЗДЕСЬ НЕТ НАРОЧНО, и это не забывчивость. Карточка вариантов — не
   всплывающее окно на секунды: она висит внизу чата всё то время, пока Клод
   ждёт ответа, то есть платить за неё пришлось бы каждый кадр и одновременно с
   пятью другими размытыми слоями экрана. Читаемость держим иначе — более
   плотной тёмной основой (--glass-base-soft, та же, что у плашки проигрывания
   голосового: она заведена ровно для случая «поверхность стеклянная, но свои
   кнопки обязаны читаться поверх едущего текста»).
   ПОЛЯ ПО БОКАМ УБРАНЫ по той же причине, что у меню: строки идут от края до
   края, а от края их отбивают собственные поля строки. */
.qbar {
  padding: 4px 0;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .10), rgba(255, 255, 255, .038)),
    var(--glass-base-soft);
  background-origin: border-box;   /* ровный волосок по кругу — см. раздел 3а */
  border: 1px solid var(--glass-edge);
  border-radius: var(--pop-radius);
  box-shadow: var(--glass-bevel), var(--glass-drop);
}
/* ── 3в. Стекло на главном экране: КАРТОЧКА ЛИМИТОВ ──
   ПОЗЖЕ ВЛАДЕЛЕЦ ОТКАЗАЛСЯ ОТ СТЕКЛА У ПЛИТОК И ПИЛЮЛЬ («выпуклые, хочу плоские»,
   см. следующий раздел) — здесь остался только верхний блок главного экрана.
   Всё, что написано ниже про стекло, к плиткам и пилюлям больше НЕ относится.
   Владелец: «сейчас матовые, выбиваются». Разница с чатом объяснена и принята:
   в чате стекло играет, потому что под островками ПРОЕЗЖАЮТ сообщения, а на
   главном под карточками только фон. Отсюда три вещи разом, поодиночке ни одна
   не сработала бы:
   1) ЗАЛИВКА стеклянная — светлый переход сверху вниз вместо плоского --panel;
   2) ПОДЛОЖКА усилена (см. --look-bg): переход под карточками стал заметен
      глазом, и стеклу наконец есть что показывать;
   3) ЛЕНТА ЗАЕЗЖАЕТ ПОД ШАПКУ (см. правило .screen ниже) — при прокрутке
      карточки уходят под островки и просвечивают, как в чате.
   РАЗМЫТИЯ ЗДЕСЬ НЕТ, А ОСВЕТЛЕНИЕ ЕСТЬ — и это главная находка этого захода.
   Стеклу от подложки нужны две разные вещи: РАЗМЫТЬ картинку и ОСВЕТЛИТЬ её,
   подняв насыщенность. На главном под карточками не картинка, а ровный переход
   цвета: размывать в нём НЕЧЕГО — размытый переход выглядит ровно как исходный
   (проверено снимками, разницы нет вообще). А вот осветление даёт ровно тот вид
   «подсвеченного изнутри» стекла, ради которого всё затевалось.
   Поэтому берём только осветление, без гауссова размытия. Дорогая часть фильтра —
   именно размытие (ядро на каждый пиксель каждый кадр); осветление и насыщенность
   — простое умножение цвета. Отсюда стекло на прокручиваемом экране без той
   платы, из-за которой размытие здесь и было запрещено.
   ВНИМАНИЕ: карточка лимитов и плитки продублированы в заготовках первого кадра
   (index.html) — правишь здесь, поправь и там, иначе будет скачок при загрузке.
   В заготовках фильтра нет намеренно: в первом кадре осветлять ещё нечего.
   ДОЛГ: заготовка плитки проекта в index.html осталась стеклянной (переход и
   тень). Общий файл стилей приходит следом и кладёт плоский вид поверх, поэтому
   держится ровно один кадр серых заготовок при холодном запуске — но выправить
   заготовку надо, чтобы скачка не было вовсе. */
.lz-lim {
  background: linear-gradient(180deg, rgba(255, 255, 255, .105), rgba(255, 255, 255, .042));
  background-origin: border-box;   /* ровный волосок по кругу — см. раздел 3а */
  border-color: var(--glass-edge);
  -webkit-backdrop-filter: saturate(1.8) brightness(1.22);
  backdrop-filter: saturate(1.8) brightness(1.22);
  /* Тот же блик по верхней грани и тень по нижней, что у островков: карточка
     обязана читаться как предмет с толщиной, а не как светлое пятно. */
  box-shadow: var(--glass-bevel), var(--glass-drop);
}
.lz-lim { border-radius: 20px; }
/* ── ПЛИТКИ ПРОЕКТОВ И ПИЛЮЛИ ЧАТОВ — ПЛОСКИЕ ──
   Владелец: «на главной чаты, эти пилюли, они как будто выпуклые. Хочу привести
   к виду, как в верхнем меню, то есть плоскими». Выпуклость давали ТРИ вещи
   сразу, и убрать надо было все три — по отдельности каждая тянула объём назад:
   1) ЗАЛИВКА ПЕРЕХОДОМ (светлее вверху, темнее внизу) — это рисованный свет
      сверху, то есть прямая имитация выпуклости;
   2) ТЕНЬ ПОД НИЖНИМ КРАЕМ (--glass-drop, 0 6px 18px) — предмет «приподнят» над
      экраном; именно её глаз читает как объём в первую очередь;
   3) ОСВЕТЛЕНИЕ ПОДЛОЖКИ (brightness) — с ним край пилюли светится изнутри.
   Осталась ровно плоская подложка одной плотностью и тот же волосок по контуру,
   что был. Форму (скругление, поля, размеры) не трогаем — правка про объём, а не
   про раскладку. Про --glass-bevel: он уже пустой (владелец ранее снял объём у
   островков теми же словами) — здесь снимается то же самое у главного экрана.
   ПЛОТНОСТЬ .085 — НЕ НА ГЛАЗ. Прежний вид давал переход .105→.042 (в среднем
   .0735) ПОВЕРХ подложки, осветлённой на 1.22. Убрав осветление, ту же светлоту
   плитки держим чуть более плотной заливкой: .085 (та же плотность, что у
   заготовок --sk и у панели записи). Плитки не потускнели, объём ушёл.
   ЗАЛИВКА ПО ВНУТРЕННЕЙ КОРОБКЕ (background-origin здесь не нужен): волосок
   темнел снизу оттого, что под рамку попадал ПОВТОР перехода (см. раздел 3а).
   Заливка ровная — повторять нечего, шва нет.
   Карточка лимитов выше осталась стеклянной намеренно: владелец говорил про
   пилюли и плитки, а не про неё. */
.lz-fold,
.lz-pill {
  background: rgba(255, 255, 255, .085);
  border-color: var(--glass-edge);
  box-shadow: none;
}
/* ВТОРОСТЕПЕННЫЕ ПОДПИСИ КАРТОЧКИ ЛИМИТОВ — ЧИТАЕМЫЕ, А НЕ ПРИЗРАЧНЫЕ.
   «обнулится через 46 минут», «обнулится через 4 дня» и остаток дней подписки
   брали общий приглушённый цвет (--dim, #8B8B93). На ПРЕЖНЕЙ плоской подложке
   (--panel, #17171A) он ещё читался; в новом виде под карточкой лежит светлое
   стекло поверх подсвеченной подложки — фон под текстом стал светлее, разница
   с ним упала, и владелец сказал прямо: «читается очень плохо».
   Поднимаем только эти подписи и только в новом виде. Не до цвета основного
   текста (--text, #C9C9CF): подписи обязаны остаться второстепенными — название
   лимита и процент должны читаться первыми. #ADADB6 — ровно посередине между
   приглушённым и основным. */
.lz-ring-sub,
.lz-plan-left { color: #ADADB6; }
/* Выбранный проект и пилюля «+ чат» остаются собой: у первого акцентная подложка
   и обводка, у второй — пунктир вместо заливки (это кнопка-заготовка, не чат).
   ЗАЧЕМ ЭТИ ПРАВИЛА ВООБЩЕ НУЖНЫ: общее правило выше сильнее прежних (у него в
   селекторе ещё и признак вида), поэтому оно молча съедало и акцентную обводку
   выбранного проекта, и подсветку нажатия. Возвращаем их поимённо. */
.lz-fold.sel {
  /* Выбранный проект тоже ПЛОСКИЙ: был переход .26→.13 (тот же рисованный свет
     сверху), стала ровная акцентная заливка той же средней плотности. */
  background: rgba(138, 160, 240, .2);
  border-color: var(--orange);
}
/* НАЖАТИЕ ЗДЕСЬ БОЛЬШЕ НЕ КРАСИТСЯ ОБЛИКОМ. Было: своя подложка акцентом у
   карточки лимитов, пилюли чата и плитки папки — по правилу на каждую. Теперь
   отклик один на всё приложение: просадка плюс заливка акцентом (сторожка
   js/press.js). Правило снято и в общей части файла, и здесь: облик на отклик не
   влияет, он одинаков в стекле и в классическом. */
/* Пунктир «+ чат» — акцентный, как и был: общее правило выше перебивало ему и
   заливку, и цвет обводки, и кнопка-заготовка теряла свой вид. */
.lz-pill-add {
  background: none; box-shadow: none; border-color: var(--orange);
}

/* ── 4. Лента чата ──
   Своя реплика — пузырь (полупрозрачный, без размытия: сквозь него видна общая
   подложка, а листание не пересчитывает ни одного слоя).
   ОТВЕТ КЛОДА — БЕЗ ПУЗЫРЯ, ВО ВСЮ ШИРИНУ. Это главная правка по ощущению:
   ответы длинные, в пузыре при ширине 82% они читаются стеной, а строка во всю
   ширину шире примерно на пятую часть — текст дышит. Пузырь тут и не нужен:
   он отвечает на вопрос «кто сказал», а на этот вопрос уже отвечает сторона —
   своё справа, ответ слева. */
.snapshot .bubble-them {
  background: none;
  box-shadow: none;
  border-radius: 0;
  max-width: 100%;
  /* ПОЛЕЙ ПО БОКАМ ЗДЕСЬ БОЛЬШЕ НЕТ — их держит сама лента (padding 16 у
     .screen.screen-chat > .snapshot выше). Раньше 14 несла эта строка, и
     получалось, что отступ есть только у текста ответа: пузырь владельца,
     карточка файла и черта между ответами доезжали до 2-й точки экрана. Теперь
     линия одна на всех, слева и справа поровну.
     Поле СНИЗУ (8) — не украшение: у пузыря оно было (9), и без него последняя
     строка последнего ответа подъезжала бы к полю ввода на те же 9 точек ближе.
     Расстояние «последнее сообщение — поле ввода» владелец уже принял (51 точка
     по коробке ряда), ломать его правкой пузырей нельзя. */
  padding: 0 0 8px;
  flex: 1 1 auto; min-width: 0;
}
/* Дневник действий (⚙ …) идёт в ленте вплотную за ответом и обязан стоять на
   той же левой линии — своё поле ему теперь не нужно, линию держит лента. */
.snapshot .acts { margin-left: 0; }
/* КНОПКА ОЗВУЧКИ — ВНИЗУ ОТВЕТА. Один заход она простояла у ВЕРХНЕЙ строки:
   там её держали, потому что у нижнего края она съезжала на каждом дописывании
   ответа и прыгала. Размен оказался плохим — при длинном ответе владелец читает
   низ, а за кнопкой приходилось листать вверх.
   Причину прыжков убрали в другом месте (chats.js): динамик выдаётся только
   ЗАВЕРШЁННОМУ ответу, у растущего его нет вовсе — значит двигаться нечему.

   ДИНАМИК БОЛЬШЕ НЕ ОТЪЕДАЕТ ШИРИНУ У ТЕКСТА. Замер на 393: кнопка стояла в
   ряду обычным соседом, и ряд отдавал ей 38 точек НА ВСЮ ВЫСОТУ ответа — текст
   переносился на 60-й точке от правой кромки, начинаясь с 16-й от левой. Это и
   есть главная часть «прижато к левому краю»: ответ во всю ширину на деле шёл в
   колонке, съехавшей влево на 44 точки. Хуже того, ширина колонки МЕНЯЛАСЬ:
   у растущего ответа кнопки нет, у завершённого есть — в конце хода весь ответ
   перевёрстывался заново.
   Теперь кнопка лежит АБСОЛЮТОМ в правом нижнем углу ряда и ширины не занимает
   вовсе, а от наезда на последнюю строку спасает распорка .tts-slot, которую
   chats.js кладёт в конец текста ответа (приём как у времени в мессенджерах).
   Ширина колонки текста от появления кнопки не меняется ни на точку.
   right: -6px — вынос на поле ленты: сама кнопка держит внутреннее поле 6, и
   значок встаёт ровно на ту же линию 16, что и текст ответа. */
.snapshot .row.them { position: relative; }
.snapshot .row.them .tts-btn {
  position: absolute; right: -6px; bottom: 0; margin: 0;
}
/* Место под значок в последней строке ответа. Ширина = кнопка (32) + воздух (4):
   последняя строка не подлезает под динамик. Высота 1px — своей строки распорка
   не создаёт. */
.snapshot .tts-slot {
  display: inline-block; width: 36px; height: 1px;
}
/* Ответ кончается блоком кода (в него распорку не вставить — там дословный
   текст): отводим значку собственную полоску под ответом. */
.snapshot .tts-slot-row { height: 22px; }
/* Пока ответ проявляется по букве, динамика не видно: он и так относится к
   ЗАВЕРШЁННОМУ ответу, а visibility (в отличие от display) не трогает раскладку —
   когда печать закончится, ничего не сдвинется. */
.snapshot .row.them.typing .tts-btn { visibility: hidden; }
/* ОТБИВКА МЕЖДУ ОТВЕТАМИ ПОДРЯД. Без пузырей два ответа слились бы в простыню,
   поэтому между ними воздух и тонкая черта во всю ширину. Только между ответами:
   после своей реплики граница уже есть — пузырь. Дневник действий (.acts) в
   ленте стоит между ответами того же хода, поэтому пара «дневник → ответ»
   отбивается так же. */
.snapshot .row.them + .row.them,
.snapshot .acts + .row.them {
  margin-top: 22px; padding-top: 20px;
  border-top: 1px solid rgba(255, 255, 255, .07);
}
.snapshot .bubble-me {
  background: rgba(138, 160, 240, .2);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .07);
  border-radius: 20px; border-top-right-radius: 6px;
}
/* Воздуха между сообщениями чуть больше: плотная лента — вторая половина
   ощущения тяжести (первая — рамки). На высоту экрана это не влияет: лента
   прокручивается внутри себя, ряд ввода стоит на месте. */
.snapshot .row { margin: 13px 0; }
/* Появление НОВОГО сообщения: чуть всплывает снизу и проступает. Класс msg-in
   вешает chats.js и ТОЛЬКО на реально новые узлы — не на всю ленту: опрос идёт
   раз в полторы секунды, и анимация всей ленты читалась бы как непрерывное
   мерцание. Прирост текста в ответе Клода не анимируется вовсе (см. chats.js). */
@keyframes look-msg-in {
  from { opacity: 0; transform: translateY(12px) scale(.985); }
  to   { opacity: 1; transform: none; }
}
/* Кроме пузырей тем же ходом появляются карточка выданного файла и дневник
   действий — они приходят в ленту вместе с ответом и обязаны вести себя так же. */
.snapshot .row.msg-in,
.snapshot .acts.msg-in,
.file-card.msg-in {
  animation: look-msg-in .34s var(--spring) both;
}

/* ── 5. Упругость ──
   ПРОСАДКУ ПОД ПАЛЬЦЕМ ЗДЕСЬ БОЛЬШЕ НЕ ДЕЛАЕМ, и это осознанная замена, а не
   потеря. Раньше ниже стоял ручной список из двух десятков селекторов с
   `:active` — он работал только в этом облике и только на тех кнопках, которые
   кто-то вспомнил дописать; половина приложения отклика не имела вовсе, а на
   айфоне `:active` встаёт не в момент касания, а когда система убедится, что
   это не прокрутка (те самые «мёртвые» кадры, на которые жаловался владелец).
   Теперь просадку даёт одна сторожка на корне документа (js/press.js): она
   ловит МОМЕНТ КАСАНИЯ, узнаёт нажимаемое по природе элемента и работает в
   ОБОИХ обликах. Дописывать сюда новые кнопки не надо — их подхватит сторожка.
   Правило ниже осталось и трогать его нельзя: сторожка ведёт только преобразование
   и свой узел заливки, а плавную смену ЦВЕТА самих элементов (подложка, обводка,
   прозрачность — например когда пилюля становится выбранной) по-прежнему держит
   оформление. Уберёшь — цвета начнут переключаться рывком.
   С заливкой отклика это правило не спорит: она лежит отдельным узлом ВНУТРИ
   элемента и своей подложки ему не меняет. */
.tile,
.sgroup-head,
.lz-pill,
.lz-fold,
.lz-addfold,
.lz-lim[role="button"],
.lz-row,
.cp-row,
.km-row,
.km-settings,
.win-item,
.btn,
.chip,
.tab,
.font-step,
.burger,
.topbar-act,
.file-card,
.term-input-row .term-go,
.term-input-row .term-stop,
.term-input-row .term-mic,
.term-input-row .term-mail,
.term-input-row .term-clip,
.tts-auto-pill,
.read-play,
.rec-done,
.rec-x {
  transition: transform .34s var(--spring), background-color .18s ease,
              border-color .18s ease, opacity .18s ease;
}
/* ══════════ ОТКЛИК НА НАЖАТИЕ — ОДИН НА ВСЁ ПРИЛОЖЕНИЕ ══════════
   Образец выбрал владелец — круглые кнопки шапки: под пальцем круг под значком
   ЦЕЛИКОМ заливается акцентом, а сам значок чернеет. Плотная заливка формы плюс
   смена цвета содержимого на противоположный. Полупрозрачной плёнки, которая
   лежала здесь раньше, больше нет: владелец увидел её рядом с образцом и
   отказался.
   Оба узла (заливка и ореол) создаёт и убирает сторожка js/press.js; она же
   решает, что заливать, — круг под значком или всю форму элемента. Здесь только
   вид. Именных правил на отдельные кнопки в файле БОЛЬШЕ НЕТ НИГДЕ: собственные
   `:active` были вычищены по всему файлу, иначе нажатия снова разъедутся — на
   это владелец и жаловался.

   ПОЧЕМУ ОТДЕЛЬНЫЕ УЗЛЫ, А НЕ СВОЙСТВА САМОЙ КНОПКИ (разобрано подробно в шапке
   js/press.js, здесь коротко):
     • яркость (filter) на кнопке гасит РАЗМЫТИЕ ЕЁ ПОДЛОЖКИ — стекло островков
       шапки и капсулы ввода мигало бы при каждом нажатии;
     • своя тень стёрла бы чужие, а тени тут значащие: стеклянная кромка,
       «толщина» островка, ободок связи с Маком на кружке меню;
     • подмена подложки самой кнопки затёрла бы её собственную (стекло, панель,
       перивинклевый диск), и вернуть её потом «как было» нечем.

   ПОЧЕМУ УЗЕЛ ЛЕЖИТ ВНУТРИ КНОПКИ, А НЕ ПОВЕРХ СТРАНИЦЫ. Раньше он лежал в теле
   документа и НАКЛАДЫВАЛСЯ на место кнопки — а значит на каждое касание кнопку
   надо было померить, прочитать её скругление и заставить браузер пересчитать
   раскладку. В чате под этим пересчётом сотни узлов ленты, и отклик сам добавлял
   ту задержку, которую был должен убрать. Ребёнку мерить нечего: место даёт
   родитель (inset), скругление берётся у него же (inherit), а просадка кнопки
   везёт заливку с собой.
   Оба узла нажатий не перехватывают (pointer-events: none) и исчезают сами. Живут
   вне обликов: отклик одинаков и в стекле, и в классическом. */
.kr-veil, .kr-ring {
  position: absolute;
  pointer-events: none;
  box-sizing: border-box;
}
/* ── Заливка формы ──
   z-index:-1 — ПОД содержимым и НАД собственной подложкой кнопки. Заливка теперь
   плотная: лёжа сверху она просто закрасила бы значок и подпись. Отдельным слоем
   кнопку делает преобразование, которое сторожка ставит всегда, — без него
   отрицательный слой провалился бы под подложку соседней карточки.
   Плотность (--kr-fill) ставит сторожка по выбранной силе отклика.
   Появление — АНИМАЦИЯ, а не переход: у только что вставленного узла нет
   «предыдущего» стиля, и переход потребовал бы принудительного пересчёта
   раскладки прямо на пути касания. Анимация начинается с первого кадра сама. */
.kr-veil {
  inset: 0;
  border-radius: inherit;
  background: var(--orange);
  opacity: var(--kr-fill, 1);
  z-index: -1;
  animation: kr-fill-in .08s ease-out both;
}
@keyframes kr-fill-in { from { opacity: 0; } to { opacity: var(--kr-fill, 1); } }
/* Ореол — только у «чёткого». Расходится и гаснет за пятую долю секунды.
   Выходит на 3 точки наружу, поэтому у него свой отступ. Ему слой не меняем: это
   тонкая линия, содержимое она не закрывает. */
.kr-ring {
  inset: -3px;
  border-radius: inherit;
  border: 2px solid var(--orange);
  animation: kr-ring-out .2s ease-out both;
}
@keyframes kr-ring-out {
  from { opacity: .65; transform: scale(1); }
  to   { opacity: 0;   transform: scale(1.16); }
}
/* Системный запрет движения: ореол — чистое движение, и без него отклик остаётся
   заливкой (её сторожка даёт всегда, и просадку в этом случае не делает). */
@media (prefers-reduced-motion: reduce) { .kr-ring { display: none; } }

/* ── Круг под значком (строка меню, строка окна: значок слева, подпись справа) ──
   ЧИСЛО 40 — НЕ ИЗ ГОЛОВЫ: это поперечник круга у кнопок шапки, то есть ровно
   образец, который выбрал владелец. Круг один и тот же везде, поэтому и место
   значка мерить не надо: он ставится серединой на середину значка, а значки в
   приложении мельче (22 в строке окна, 34 в боковом меню, 40 у шестерёнки).
   Форму значка при этом НЕ повторяем нарочно: у плашки бокового меню своё
   скругление 10, у места в строке окна нет никакого, а круг должен читаться
   одинаково — иначе снова разнобой.
   ПОПРАВКА (плашки бокового меню стали круглыми): у .km-chip теперь тоже круг,
   поэтому там подсветка и плашка сошлись концентрически (34 и 40 — кольцо в 3
   точки). Правило ниже от этого не меняется: круг по-прежнему один на всё
   приложение и по-прежнему не повторяет форму значка там, где она другая
   (строка окна .wi-ic — вообще без плашки). */
.kr-veil.kr-halo, .kr-ring.kr-halo {
  inset: auto; top: 50%; left: 50%;
  width: 40px; height: 40px; margin: -20px 0 0 -20px;
  border-radius: 50%;
}
.kr-ring.kr-halo { width: 46px; height: 46px; margin: -23px 0 0 -23px; }
/* Место значка на время нажатия держит круг: своя опора для наложения и свой
   слой (иначе круг с отрицательным слоем ушёл бы под подложку строки). Класс
   ставит и снимает сторожка; размеров он не меняет. */
.kr-slot { position: relative; z-index: 0; }

/* ── Содержимое на заливке — чёрным ──
   В образце значок чернеет; то же делаем со всем, что оказалось на заливке.
   ТРИ ПРАВИЛА, А НЕ ОДНО, потому что значки в приложении раскрашены по-разному:
   одни берут цвет у строки (stroke="currentColor"), у других цвет записан прямо
   в рисунке (значки бокового меню — синие, зелёные, серые), а третьи залиты
   (точки на значке сервера). Перебирать их поимённо — тот самый список, который
   забывают пополнить; выборка по природе достаёт их всех разом.
   `:not([... ="none"])` обязателен: у контурных значков стоит fill="none", и без
   этой оговорки они залились бы чёрным пятном вместо контура.
   `!important` здесь не «на всякий случай»: цвета значков записаны прямо в
   рисунке (это атрибут, но правило файла его перебивает только с весом), а
   подписи красят свои правила с более длинными селекторами. Класс живёт ровно
   те миллисекунды, что палец на кнопке. */
.kr-ink, .kr-ink * { color: #000 !important; }
.kr-ink [stroke]:not([stroke="none"]) { stroke: #000 !important; }
.kr-ink [fill]:not([fill="none"]) { fill: #000 !important; }

/* Появление всплывающей надписи — с той же пружиной, а не линейно. Кадров
   плотности (look-fade-in) здесь больше нет: их читало только окно записи, и
   ушли они вместе с расхлопыванием обрезкой (см. раздел ниже). */
@keyframes look-pop-in {
  from { opacity: 0; transform: translateY(10px) scale(.955); }
  to   { opacity: 1; transform: none; }
}
/* Проявление затемнения под окном раньше стояло здесь отдельным числом (.18 с,
   ease). Теперь оно идёт теми же кривыми и той же длительностью, что и сама
   панель, и потому переехало вниз, в раздел «ПУЗЫРЬ» — к остальным числам
   движения окна. Второго правила о нём в файле нет. */
.toast {
  border-radius: 16px;
  animation: look-pop-in .3s var(--spring) both;
}

/* ════════ РАСХЛОПЫВАНИЕ ОБРЕЗКОЙ — СНЯТО ЦЕЛИКОМ (11 августа 2026) ════════
   Здесь жил второй способ появления: панель полного размера, у которой едет
   обрезка (clip-path) от точки роста — кадры pop-open, кривые --pop-in-scale /
   --pop-in-fade и покадровое проявление look-fade-in. Его единственным
   пользователем оставалось окно записи голоса, и владелец его забраковал на
   живом айфоне: обрезка от микрофона в правом нижнем углу читалась как
   обрезанный левый бок, а отдельный ход плотности — как второе окно поверх
   первого.
   Способ появления в приложении теперь ОДИН, и он не в стилях, а в js/win.js
   (growWindow): окно целиком летит из нажатой кнопки преобразованием, содержимое
   отдельно проступает из размытия. Обрезать край нечем — резать нечего.
   Правила, ушедшие вместе с механизмом: :root{--pop-in-scale, --pop-in-fade},
   @keyframes pop-open, @keyframes look-fade-in, --pop-cx/--pop-cy у окна записи
   и его собственная строка animation. */

/* Системный запрет анимаций уважаем: вид остаётся, движение выключается.
   Закрытие при этом снимает узел сразу, не дожидаясь картинки (см. js/ui.js). */
@media (prefers-reduced-motion: reduce) {
  .snapshot .row.msg-in,
  .toast { animation: none; }
}

/* ── 6. Лёгкость: воздух, мягкие углы, меньше видимых линий ──
   Обводки стали еле заметными сами (переменная --border выше). Здесь — воздух
   между блоками и более мягкие углы: тёмный экран с частой сеткой прямых рамок
   и был тем «тяжёлым», на что жаловался владелец.
   ВНИМАНИЕ: всё, что меняет РАЗМЕРЫ на главном экране, продублировано в
   index.html (заготовки первого кадра) — иначе при загрузке был бы скачок. */
.lz-sec { margin: 26px 0 14px; }
.lz-sec:first-child { margin-top: 2px; }
.lz-pills { gap: 12px; }
.lz-fold { border-radius: 18px; }
.tile { border-radius: 18px; margin-bottom: 14px; }
/* Строка-заголовок раскрывающегося раздела настроек живёт по тем же правилам,
   что и плитка: те же мягкие углы и тот же воздух между строками. */
.sgroup-head { border-radius: 18px; }
.sgroup { margin-bottom: 12px; }
.file-card,
.perm-card,
.queue-item,
.feed-line,
.ctx-card { border-radius: 18px; }
/* Поле поиска по чатам. Строка поиска ВНУТРИ чата (.find-bar) сюда больше не
   входит: она стала плавающим островком со своей стеклянной заливкой (раздел 3б),
   и это правило её перебивало бы. */
.srch-field { background: rgba(255, 255, 255, .045); }
/* Заготовки («серые формы» до прихода данных) и полосы-шкалы брали цвет из
   --border, а он теперь почти прозрачный — сами по себе они бы исчезли. Задаём
   им плотность отдельно: это не обводка, а видимая заливка. */
.sk { background: rgba(255, 255, 255, .085); }
.bar { background: rgba(255, 255, 255, .09); }
.lz-ring-track { stroke: rgba(255, 255, 255, .11); }
.term-input-row.has-mic:not(.compose) .term-go {
  background-color: rgba(255, 255, 255, .14);
}
.lz-ring-sep,
.lz-sec-line { background: rgba(255, 255, 255, .08); }
/* Заливка кружка меню и капсулы кнопок задана выше, в разделе «островки шапки»:
   там она тёмная и плотная (под ними теперь едет лента, а не ровный фон), там же
   у них размытие и кромка. Отдельного правила здесь больше нет — иначе оно
   перебивало бы заливку островков и они снова стали бы призраками. */
.find-bar { border-radius: 999px; }

/* ── ЗДЕСЬ БЫЛА НАСТРОЙКА «РАСХЛОПЫВАНИЕ» — В ОФИСЕ ЕЁ НЕТ И ЗАВОДИТЬ НЕ НАДО ──
   В старом приложении признак <html data-pop="grow"> включал второй ход
   появления: окна и панель записи росли масштабом из нажатой кнопки. В Офисе вид
   ОДИН и действует безусловно (см. index.html: «ветвления по виду здесь больше
   нет»), признак не ставит никто, и все правила под ним не работали ни разу —
   вместе с ними лежала мёртвой и вторая скорость панели записи (340 мс), и её
   схлопывание (.rec-gone). Снято целиком: 471 строка правил, кадров и кривых.
   Панель записи появляется одним ходом — см. «Оверлей записи голоса» выше.
   Работающий образец, если он когда-нибудь понадобится, остался в старом
   приложении (bridge/webroot/css/app.css). */

/* ════════════ ОКНО ПО ЭТАЛОНУ ВЛАДЕЛЬЦА ════════════
   Владелец сверстал окно сам, отдельной самодостаточной страницей, и потребовал
   перенести его В ПРИЛОЖЕНИЕ ОДИН В ОДИН — вид, размеры, шрифты, обе анимации,
   вложенные списки. Указание прямое и повторённое дважды: подгоняются ОКНА
   ПРИЛОЖЕНИЯ под эту вёрстку, а не вёрстка под окна. Поэтому всё, что раньше
   отвечало за вид и движение всплывающего окна, снято целиком:
     • затемнение и стекло окна из оформления «Стекло»;
     • раскрытие обрезкой (clip-path) и волна по строкам;
     • рост масштабом за настройкой «Расхлопывание окон» — у ОКОН его больше нет
       (у панели записи голоса он остался: это не всплывающее окно, оно живёт
       внизу чата и своей вёрстки владельца у него нет).
   Ни одно число этого раздела не зависит ни от одной настройки: вариантов
   появления у окна больше не существует, он один.

   ЧТО СЧИТАЕТ КОД, А ЧТО ОФОРМЛЕНИЕ. Место окна, обе анимации и высоту сцены
   считает сборщик (js/ui.js → openWindow): в эталоне они сделаны через
   element.animate(), а не правилами, и владелец это утвердил. Здесь — только
   покой: поверхность, строки, кромки, разделители.

   ── ПОТОЛОК ВЫСОТЫ И ПРОКРУТКА (ЕДИНСТВЕННОЕ, ЧЕГО В ЭТАЛОНЕ НЕТ) ──
   Слова владельца: ширина одинаковая на всех окнах; высота по содержимому, но
   если пунктов много — окно не выше, чем в его примере, дальше внутри прокрутка.
   В самом эталоне окно растёт без предела, поэтому потолок посчитан по его же
   числам — по самому длинному виду его примера (главный вид):
       строка «Модель»            44
       разделитель                 0.5
       строка «Скорость»          44
       заголовок «ИНТЕЛЛЕКТ»      34
       пять строк выбора       5×44 = 220
       четыре разделителя      4×0.5 = 2
                              ИТОГО 344.5
   Это и есть --mw-cap. Сверху он ещё зажат видимым экраном минус безопасные
   поля: на маленьком экране окно не должно вылезать под «бровь» и под домашний
   индикатор, даже если 344.5 туда не помещаются.
   Число оставлено ровно таким и после того, как разделители из эталона исчезли:
   потолок утверждён владельцем как потолок, а не пересчитывается заново при
   каждой правке вида. */
:root {
  --mw-radius: 19px;
  --mw-text: #ffffff;
  --mw-secondary: #8e8e93;
  --mw-line: rgba(255, 255, 255, .12);
  --mw-press: rgba(255, 255, 255, .1);
  --mw-blue: #0a84ff;
  --mw-spring: cubic-bezier(0.32, 0.72, 0, 1);
  --mw-cap: min(344.5px, calc(var(--app-h, 100dvh) - var(--safe-top, 0px)
    - env(safe-area-inset-bottom) - 16px));
}
/* Затемнение позади: чёрный 35 % и размытие 5 (эталон). Отдельный узел внутри
   подложки-ловца — у него свой ход прозрачности, не общий с окном. */
.mw-scrim {
  position: absolute; inset: 0;
  visibility: hidden; opacity: 0;
  background: rgba(0, 0, 0, .35);
  -webkit-backdrop-filter: blur(5px);
  backdrop-filter: blur(5px);
  transition: opacity 320ms ease, visibility 0s linear 320ms;
}
.mw-scrim.is-visible { visibility: visible; opacity: 1; transition: opacity 320ms ease; }
/* ПОВЕРХНОСТЬ ОКНА — ОДНА НА ВСЁ ПРИЛОЖЕНИЕ. Все числа — из эталона: скругление
   19, фон rgba(30,30,32,.75), размытие подложки 50 с насыщенностью 200 %,
   тройная тень (крупная мягкая + контактная + внутренний светлый кант в
   полпикселя), рамка rgba(255,255,255,.2) в полпикселя.
   ВТОРЫМ ЗДЕСЬ СТОЯЛО ОКНО ЗАПИСИ ГОЛОСА — и ушло вместе с самим окном
   (11 августа 2026): запись живёт слоем внутри капсулы ввода, а капсула своя
   поверхность и есть. Поверхность окна снова ровно одна, и это хорошо: третий
   вид окна разъехался бы с первыми двумя на первой же правке. */
.modal.mw {
  overflow: hidden;
  border: 0.5px solid rgba(255, 255, 255, .2);
  border-radius: var(--mw-radius);
  background: rgba(30, 30, 32, .75);
  box-shadow:
    0 24px 50px rgba(0, 0, 0, .6),
    0 4px 12px rgba(0, 0, 0, .35),
    inset 0 0.5px 0.5px rgba(255, 255, 255, .3);
  -webkit-backdrop-filter: blur(50px) saturate(200%);
  backdrop-filter: blur(50px) saturate(200%);
  transform-origin: center;
  will-change: transform, opacity;
}
/* Остальное — про то, что окно ВСПЛЫВАЮЩЕЕ: место, ширина эталона (min(250,
   экран − 16)), скрытость до раскрытия и вид текста списка. Окна записи это не
   касается: у него своё место внизу разговора и свои крупные кнопки.
   Двойной класс — чтобы перебить поля и ширину, оставшиеся у отдельных окон
   (панель чатов, словарь): у эталона поля держат сами строки, а не окно. */
.modal.mw {
  position: fixed; z-index: 110;
  width: min(250px, calc(100vw - 16px));
  max-width: none;
  margin: 0; padding: 0;
  visibility: hidden; opacity: 0;
  color: var(--mw-text);
  text-align: left;
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display",
    "Helvetica Neue", Arial, sans-serif;
}
.modal.mw.is-mounted { visibility: visible; opacity: 1; }
/* СЦЕНА — то, у чего плавно едет высота при переключении видов (320 мс, пружина
   эталона). Её высоту ставит сборщик числом в точках: проценты и auto здесь не
   анимируются.
   ПРИ РАСКРЫТИИ этот ход гасится сборщиком (js/ui.js → fitStage): высота ставится
   мгновенно, иначе к раскрытию примешивается второе движение и окно улетает за
   край экрана. Укорочение хода с 600 до 320 этого не отменяет — гашение сделано
   выключением перехода, а не расчётом на его длительность. */
.mw-stage {
  position: relative; width: 100%; height: 0;
  overflow: hidden; opacity: 0;
  transition: height 320ms var(--mw-spring);
  will-change: height, opacity, transform, filter;
}
/* ВИДЫ: главный и подменю. Переключение как в родных настройках айфона — текущий
   уходит влево на 30 % и гаснет, новый приезжает справа.
   ПРОКРУТКА ЖИВЁТ ЗДЕСЬ, а не у окна и не у сцены: сцена обрезает по своей
   едущей высоте, а вид — единственный узел, у которого есть и потолок, и всё
   содержимое. Инерция — -webkit-overflow-scrolling: touch. */
.mw-view {
  position: absolute; top: 0; left: 0; width: 100%;
  /* Потолок эталона (344.5) И запас места рядом с кнопкой: окно стоит у своей
     кнопки, и если с той стороны до края экрана меньше потолка, оно должно
     упереться в это место, а не вылезти за экран. Запас считает сборщик
     (js/ui.js → mwPlace, переменная --mw-room); сам потолок при этом не тронут. */
  max-height: min(var(--mw-cap), var(--mw-room, 100dvh));
  overflow-y: auto; overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  padding: 14px 16px;
  opacity: 0; pointer-events: none;
  transform: translateX(100%);
  transition: transform 320ms var(--mw-spring), opacity 200ms ease;
}
.mw-view::-webkit-scrollbar { width: 0; }
.mw-view.is-current { z-index: 2; opacity: 1; pointer-events: auto; transform: translateX(0); }
.mw-view.is-behind { z-index: 1; opacity: 0; pointer-events: none; transform: translateX(-30%); }
/* СПИСКУ ПОЛЕЙ НЕ НАДО: строки в эталоне идут от края до края, а от края их
   отбивают собственные поля строки (16). Поля нужны только окнам с текстом —
   подтверждению, ожиданию, переименованию. Отличаем их по содержимому, а не
   признаком от вызова: так ни один вызов не надо помнить и править. */
.mw-view:has(> .win-item),
.mw-view:has(> .mw-titlerow),
.mw-view:has(> [role="radiogroup"]) { padding: 0; }
.cp-pop .mw-view, .viewer .mw-view { padding: 0; }

/* ── СТРОКА СПИСКА ПО ЭТАЛОНУ ──
   Высота 44, шрифт 17, межбуквенное −0.41, подсветка нажатия, разделитель 0.5 с
   отступом слева 16. Значок слева оставлен: у владельца в примере значков нет
   вовсе, значит и правила о них нет, — а выбрасывать их из приложения было бы
   переосмыслением, а не переносом. Место под значок держится всегда, чтобы
   подписи стояли ровным левым краем. */
.modal.mw .win-item {
  display: flex; align-items: center; gap: 12px;
  width: 100%; height: 44px; min-height: 44px;
  padding: 0 16px; border-radius: 0;
  color: var(--mw-text);
  font-size: 17px; font-weight: 400; letter-spacing: -0.41px;
  transition: background 120ms ease;
}
.modal.mw .win-item:active { background: var(--mw-press); }
.modal.mw .wi-ic { flex: 0 0 20px; width: 20px; height: 20px; }
.modal.mw .wi-ic svg { width: 20px; height: 20px; }
/* Длинное имя проекта в строку 44 не переносим — иначе строки станут разной
   высоты, и список перестанет быть тем ровным списком, что в эталоне. */
.modal.mw .wi-tx { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.modal.mw .win-item .chev {
  flex: 0 0 auto; display: flex; align-items: center;
  width: 7px; height: 12px; margin-left: 4px;
  color: var(--mw-secondary); opacity: .6;
}
.modal.mw .win-item .chev svg { width: 7px; height: 12px; display: block; }
/* РАЗДЕЛИТЕЛЕЙ МЕЖДУ СТРОКАМИ БОЛЬШЕ НЕТ. В обновлённом эталоне владельца черта
   между строками снята совсем (.separator { display: none }) — строки разделяет
   один только ритм высоты 44. Правило не удалено, а погашено: черту рисует общее
   правило приложения (.win-item + .win-item::before выше по файлу), и оно нужно
   спискам вне окна. */
.modal.mw .win-item + .win-item::before { display: none; }
.modal.mw .win-item.danger { color: var(--red); }
.modal.mw .win-item.disabled { opacity: .4; color: var(--mw-secondary); }
/* Отдельная черта (между разделами, под заголовком подменю) — тоже погашена:
   в обновлённом эталоне разделителей нет вовсе. Узел mwSeparator() остаётся в
   сборщике и в вызовах, чтобы не трогать разметку экранов, но ничего не рисует. */
.mw-sep, .mw-sep-full { display: none; }
/* Заголовок раздела: 12 капсом серым (эталон). */
.mw-section {
  display: flex; align-items: center; height: 34px; padding: 10px 16px 4px;
  color: var(--mw-secondary); font-size: 12px; font-weight: 400;
  text-transform: uppercase; letter-spacing: -0.01px;
}
/* Заголовок подменю: сетка 44 / 1fr / 44 с кнопкой «назад» слева (эталон). */
.mw-titlerow {
  display: grid; grid-template-columns: 44px 1fr 44px; align-items: center;
  height: 44px; font-size: 17px; font-weight: 600; letter-spacing: -0.41px;
  text-align: center;
}
.mw-back {
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; padding: 0; border: none; background: none;
  color: var(--mw-blue); cursor: pointer; -webkit-appearance: none;
}
.mw-back:active { opacity: .5; }
.mw-back svg { width: 9px; height: 15px; }
/* Галочка выбора — синяя, появляется с пружинкой (эталон). */
.mw-check {
  flex: 0 0 16px; width: 16px; height: 16px; color: var(--mw-blue);
  opacity: 0; transform: scale(.5);
  transition: opacity 140ms ease, transform 180ms var(--mw-spring);
}
.win-item.is-selected .mw-check { opacity: 1; transform: scale(1); }

/* ── ОКНА С ТЕКСТОМ (подтверждение, ожидание, переименование, «Совет») ──
   Тот же образец, только внутри не строки, а текст и кнопки. Кегли приводим к
   эталону, чтобы окно не выглядело набранным другим шрифтом. */
.modal.mw h2 { font-size: 17px; font-weight: 600; letter-spacing: -0.41px; margin: 0 0 8px; }
.modal.mw p { font-size: 15px; letter-spacing: -0.24px; margin: 0 0 10px; }
.modal.mw p:last-child { margin-bottom: 0; }
.modal.mw .btn-row { display: flex; gap: 8px; margin-top: 12px; }
.modal.mw .btn-row .btn { flex: 1 1 0; min-width: 0; }

/* ── ПАНЕЛЬ ОТКРЫТЫХ ЧАТОВ ──
   Своей ширины и своих полей у неё больше нет (были 420): ширина одна на все
   окна — требование владельца. Внутренние отступы отдаём самим кускам.
   СЛОВАРЬ ОТСЮДА УШЁЛ: он больше не окно, а экран (решение владельца), и
   правила про поля окна ему только мешали бы. */
.modal.mw.cp-pop { padding: 0; }
.cp-pop .srch-field { margin: 8px 10px 4px; padding: 0 8px 0 12px; min-height: 40px; }
.cp-pop .srch-input { font-size: 15px; padding: 9px 0; }
.cp-body { padding: 0 0 8px; margin: 0; }
/* Строки списка окон — в ритм эталона: поля 16, кегль 17, межбуквенное −0.41,
   разделитель полпикселя с отступом слева 16. Высота 44 здесь МИНИМУМ, а не
   жёсткий размер: в строке бывает вторая, приглушённая подпись, и обрезать её
   ради ровных 44 значило бы потерять сведения. */
.cp-pop .cp-row { position: relative; min-height: 44px; padding: 5px 16px; gap: 10px; }
/* Черты между строками нет и здесь — по обновлённому эталону. Рамку строки при
   этом снимаем всё так же: иначе она вернула бы линию во всю ширину. Подсветка
   текущего чата (.cp-cur) живёт на своей тени и от этого не страдает. */
.cp-pop .cp-row + .cp-row { border-top: none; }
.cp-pop .lz-row-name { font-size: 17px; font-weight: 400; letter-spacing: -0.41px; }
.cp-pop .lz-row-sub, .cp-pop .lz-row-snip { font-size: 13px; }
/* Заголовок папки — тот же заголовок раздела, что в эталоне. */
.cp-pop .cp-group {
  margin: 0; padding: 10px 16px 4px; height: 34px;
  color: var(--mw-secondary); font-size: 12px; font-weight: 400;
  text-transform: uppercase; letter-spacing: -0.01px;
}
.cp-pop .cp-cur { border-radius: 0; padding-left: 13px; }

/* ── ПРОСМОТР ФАЙЛА — ЕДИНСТВЕННОЕ ОКНО ШИРЕ ЭТАЛОНА ──
   И это осознанное отступление, а не недосмотр. Эталон владельца — про меню и
   списки; просмотр файла показывает картинку или страницу документа, и в окне
   шириной 250 его нельзя ни прочитать, ни разглядеть. Всё остальное (поверхность,
   кромка, тень, обе анимации, закрытие) у него общее с прочими окнами. */
.modal.mw.viewer { width: min(720px, calc(100vw - 16px)); }
.viewer { --mw-cap: calc(var(--app-h, 100dvh) - var(--safe-top, 0px)
  - env(safe-area-inset-bottom) - 16px); }
.viewer .mw-view { display: flex; flex-direction: column; }

/* Системный запрет движения уважаем: вид остаётся, движение выключается.
   Сборщик в этом случае и окно показывает сразу, без ходов (см. js/ui.js). */
@media (prefers-reduced-motion: reduce) {
  .mw-scrim, .mw-stage, .mw-view, .mw-check { transition: none; }
}

/* ══════════════ НОВОЕ В ОФИСЕ (фаза 7) ══════════════
   Всё, чего в старом приложении не было, потому что не было и самих узлов:
   служебные строки событийной ленты, экран отказа и плашка провала установки.
   Держится отдельным блоком в конце — чтобы при сверке с исходным файлом стилей
   было сразу видно, что добавлено, а что перенесено. */

/* Служебная строка ленты — не реплика, поэтому не пузырь: одна строка мелким
   приглушённым текстом по центру. Ход события (начал работать, вызвал действие,
   упёрся в предел) читается как пометка на полях, а не как чьё-то сообщение. */
.snapshot .row.sys { justify-content: center; margin: 6px 0; }
.snapshot .row.sys .sys-text {
  font-size: .78rem; color: var(--dim); text-align: center;
  padding: 2px 10px; border-radius: 999px;
}
.snapshot .row.sys-error .sys-text { color: var(--red, #f85149); }
/* Очистка контекста заметнее прочих пометок: она рассекает переписку.
   Индикатор работы своих правил здесь не имеет — он рисуется старым узлом
   `.working` с мигающими точками (см. выше), вид перенесён дословно. */
.snapshot .row.sys-reset .sys-text {
  color: var(--dim); border: 1px solid var(--border);
}
/* Проступание нового узла. Признак ставит сама лента по номеру события, а не
   счётчик узлов: дописывание текста в уже стоящий ответ под правило не
   подпадает — узел тот же, номер прежний. */
.snapshot .row[data-fresh] { animation: row-in .18s ease-out both; }
@keyframes row-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .snapshot .row[data-fresh] { animation: none; } }

/* Пустой список вкладок. */
.empty { color: var(--dim); font-size: .88rem; padding: 8px 2px; }

/* Экран отказа: дальше не пускают, и выход ровно один — ввести код заново. */
.stop-wrap { display: flex; flex-direction: column; gap: 16px; padding: 24px 4px; }
.stop-text { color: var(--bright); font-size: 1rem; margin: 0; }

/* Плашка «обновление не установилось». Стоит внизу экрана поверх всего: это не
   ошибка работы, а предупреждение, что телефон остался на прежней версии. */
.sw-fail {
  position: fixed; left: 12px; right: 12px; z-index: 200;
  bottom: calc(env(safe-area-inset-bottom) + 12px);
  background: var(--panel); border: 1px solid var(--border); border-radius: 14px;
  padding: 12px 14px; color: var(--text); font-size: .85rem;
}
