:root {
  --bg: var(--tg-theme-bg-color, #212121);
  --bg2: var(--tg-theme-secondary-bg-color, #0F0F0F);
  --fg: var(--tg-theme-text-color, #FFFFFF);
  --hint: var(--tg-theme-hint-color, #AAAAAA);
  --accent: var(--tg-theme-link-color, #8774E1);
  --btn: var(--tg-theme-button-color, #8774E1);
  --btn-fg: var(--tg-theme-button-text-color, #FFFFFF);
  --own: #766AC8;
  --borders: #303030;
  --dividers: #3B3B3D;
  --danger: #E64646;
  --warn: #C8A04A;
  --r-msg: 16px;
  --r-msg-sm: 6px;
  --r-def: 10px;
  --head: 48px;
  --col-head: 64px;
  --meta: 12px;
  --row: 72px;
  --ava: 54px;
  --pad: 9px;
  --gap: 8px;
  /* Отступ шапки сверху — переменной, а не числом на месте: в полном экране к нему
     прибавляется безопасное поле (`body[data-fullscreen="yes"] .top` ниже), и два
     числа `6px` в двух местах однажды разошлись бы. */
  --head-pad: 6px;
  --msg-size: 15px;
  --msg-lh: 1.3125;
  --tap: 44px;
  --col: 760px;
  --side: 424px;
  --hatch: repeating-linear-gradient(45deg, #ffffff14 0 6px, #ffffff05 6px 12px);

  /* ── Безопасные поля: ДВА СЛОЯ, три источника на слой, и нуль не считается ответом ──
     (П-60.) В полном экране страница залезает под вырез, под полосу статуса и под
     плавающие кнопки самого клиента («✕ Закрыть» слева, «⌄ ⋯» справа — их рисует он,
     ПОВЕРХ страницы). Это два разных слоя, и путать их нельзя:

     * слой ЖЕЛЕЗА — вырез, полоса статуса, нижний индикатор: `safeAreaInset`
       (Bot API 8.0), он же `--tg-safe-area-inset-*`, он же `env(safe-area-inset-*)`;
     * слой КЛИЕНТА — его собственные кнопки внутри безопасной зоны:
       `contentSafeAreaInset` (Bot API 8.0), он же `--tg-content-safe-area-inset-*`.
       У `env()` соответствия нет и быть не может: браузер про кнопки Telegram не знает.

     Каждый слой знает три источника, и порядок между ними не вкусовой:

     1. `--fs-dev-*` / `--fs-cli-*` — то, что СКРИПТ спросил у клиента и записал сюда.
        Ставится только когда клиент ответил ЧИСЛОМ больше нуля;
     2. `--tg-…-inset-*` — то, что клиент объявил сам (`telegram-web-app.js` пишет эти
        свойства в корень документа);
     3. `env(safe-area-inset-*)` — то, что знает про устройство сам браузер (только
        слой железа).

     **Между вторым и третьим `max()`, а не `var(--tg-…, env(…))`, и это главная строка
     здесь.** Клиент, который этих полей не поддерживает, присылает их НУЛЯМИ, а не не
     присылает вовсе: `var()` видит объявленный нуль, запасной путь не включается — и
     «не знаю» превращается в «занято нуль», то есть верх интерфейса уезжает под полосу
     статуса. На Android 10 то же делает и поддерживающий клиент: в полном экране он
     отдаёт `top = 0` и рисует свои кнопки поверх полосы. `max()` берёт большее, то есть
     нуль проигрывает любому знанию, а нулевой итог означает ровно одно: не знает НИ
     ОДИН источник. Эталон вида — iOS, где поля верны; Android держится запасным путём,
     а не подгонкой чисел.

     Чем платим: `max()` понимают Safari 11.1+ / iOS 11.3+ и Chrome 79+. На клиенте
     старше объявление становится негодным на вычислении, и отступ выйдет нулевым — то
     есть ровно прежнее поведение половинной шторки, где он и был нулевым. */
  --dev-top: var(--fs-dev-top,
    max(var(--tg-safe-area-inset-top, 0px), env(safe-area-inset-top, 0px)));
  --dev-right: var(--fs-dev-right,
    max(var(--tg-safe-area-inset-right, 0px), env(safe-area-inset-right, 0px)));
  --dev-bottom: var(--fs-dev-bottom,
    max(var(--tg-safe-area-inset-bottom, 0px), env(safe-area-inset-bottom, 0px)));
  --dev-left: var(--fs-dev-left,
    max(var(--tg-safe-area-inset-left, 0px), env(safe-area-inset-left, 0px)));
  --cli-top: var(--fs-cli-top, var(--tg-content-safe-area-inset-top, 0px));
  --cli-right: var(--fs-cli-right, var(--tg-content-safe-area-inset-right, 0px));
  --cli-bottom: var(--fs-cli-bottom, var(--tg-content-safe-area-inset-bottom, 0px));
  --cli-left: var(--fs-cli-left, var(--tg-content-safe-area-inset-left, 0px));

  /* ── ПОЛИТИКА СЛОЖЕНИЯ СЛОЁВ — одно место на всю страницу ──
     Решение ведущего (бриф П-60, «Замеренные факты платформы» §3): слои СКЛАДЫВАЮТСЯ.
     Довод — `contentSafeAreaInset` отсчитывается от безопасной зоны, то есть это второй
     слой НАД первым, а не его замена. Экосистема расходится: часть проектов берёт
     максимум, в одном суммирование откатили как не проверенное на живом клиенте, —
     поэтому гипотеза названа гипотезой, и различает их одно наблюдение владельца:
     лишняя пустота сверху означает, что верен максимум.
     **Смена гипотезы — четыре строки ниже и ни одной больше** (`+` → `max(…, …)`).
     Скрипт в этом решении не участвует: он сообщает слои по отдельности и ничего не
     складывает, иначе политика жила бы в двух местах и разошлась бы на первой правке. */
  --safe-top: calc(var(--dev-top) + var(--cli-top));
  --safe-right: calc(var(--dev-right) + var(--cli-right));
  --safe-bottom: calc(var(--dev-bottom) + var(--cli-bottom));
  --safe-left: calc(var(--dev-left) + var(--cli-left));
}
.tx-13 { --msg-size: 13px; }
.tx-15 { --msg-size: 15px; }
.tx-17 { --msg-size: 17px; }
* { box-sizing: border-box; }
html, body {
  margin: 0; padding: 0; background: var(--bg2); color: var(--fg);
  font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-size: 16px; line-height: 1.5;
  -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
  /* ── Почему здесь ДВА объявления одного свойства (D-274) ──
     `overflow-x: hidden` отрезает горизонтальную прокрутку, но по спецификации уводит
     вторую ось из `visible` в прокручиваемое значение — то есть делает корневой элемент
     КОНТЕЙНЕРОМ прокрутки, и `position: sticky` у шапок обоих экранов перестаёт держать:
     замер исполнителя П-54d давал шапку переписки на `y = −3430 px` вместо нуля. `clip`
     этого следствия не имеет (`visible` рядом с `clip` остаётся `visible`), а
     горизонтальную прокрутку отрезает так же — шапки встают на ноль, и прокрутки нет
     (замер П-54c в WebKit: обе шапки `y = 0` на всех положениях, `scrollWidth` 390 при
     экране 390; принудительно возвращённый `hidden` тут же даёт `y = −1200`).
     Первое объявление — не рудимент, а запасной путь: непонятое объявление браузер
     отбрасывает целиком, и без `hidden` рядом `overflow-x` остался бы `visible`, то есть
     горизонтальная прокрутка вернулась бы ровно там, где её меньше всего ждут. Где `clip`
     понят, шапки держатся; где не понят — работает прежний `hidden` (шапки не держатся,
     но прокрутки нет); не возвращается она нигде.
     **Чьё это «не понят» — по `mdn/browser-compat-data`** (`css/properties/overflow-x.json`,
     значение `clip`): Safari 16, iOS — зеркалом Safari, Chrome 90, Firefox 81. То есть на
     эталонной платформе владельца (iOS) путь — `clip`, а запасной нужен только для
     клиентов до iOS 16. **«Safari с 27» — это про `overflow-anchor`, другое свойство**
     (`test_p54d_prepend.py` цитирует его верно); у `clip` 27-й версии нет, и спутать их
     значило бы отказаться от починки из-за чужого числа. */
  overflow-x: hidden;
  overflow-x: clip;
}
body {
  /* Четыре поля одним объявлением; откуда берётся каждое — в `:root` выше. */
  padding: var(--safe-top) var(--safe-right) var(--safe-bottom) var(--safe-left);
  min-height: var(--tg-viewport-stable-height, 100vh);
}
/* На телефоне колонки идут стопкой (`block`), на широком экране — в ряд (`flex`, см.
   медиазапрос в конце файла). Так «две колонки» остаются одним набором разметки. */
.app { display: block; min-height: 100%; }
.pane { min-width: 0; background: var(--bg2); }
.pane--thread { background: var(--bg); }
/* Какая колонка видна — решает признак на `body`, а НЕ атрибут `hidden` на колонке.
   Разница принципиальная: на широком экране видны обе колонки, то есть `hidden`
   пришлось бы перебивать стилями, и «спрятанное скриптом» перестало бы прятаться.

   `no-back` — клиент без системной кнопки «назад» (Bot API младше 6.1). Своей кнопки
   возврата у страницы НЕТ и быть не должно (бриф §1: владелец ждёт системную, а две
   кнопки возврата с разным поведением — дефект). Поэтому на таком клиенте список не
   прячется вовсе: переписка встаёт под ним, и уйти из неё можно прокруткой, а не
   второй кнопкой. */
body[data-thread="open"]:not(.no-back) .pane--list { display: none; }
body:not([data-thread="open"]) .pane--thread { display: none; }
.top {
  position: sticky; top: 0; z-index: 3; background: var(--bg2);
  border-bottom: 1px solid var(--borders); padding: var(--head-pad) 8px;
}
.top--thread { background: var(--bg); display: flex; align-items: center; gap: 6px; }

/* ── Полный экран: системное место забирает ШАПКА, а не тело страницы (П-60) ──
   Признак `data-fullscreen` на `body` — три состояния словами: `yes` (дали) · `no`
   (отказали) · `unknown` (клиент младше Bot API 8.0 или ещё не ответил). Ставит его
   скрипт по ответу клиента, в разметке он стоит в состоянии `unknown`. Правила ниже
   работают ровно в ОДНОМ из трёх: и отказ, и старый клиент оставляют раскладку
   такой, какой она была до пакета, — иначе после отказа остались бы «полноэкранные»
   отступы, то есть дыра сверху в половинной шторке.

   Почему отступ достаётся шапке, а не телу: шапка ЛИПКАЯ и прижимается к верху
   вьюпорта (`top: 0`), а не к набивке тела. Отступ на теле развёл бы её с нужным
   местом сразу же — при первой прокрутке шапка встала бы под полосу статуса, то
   есть ровно то, чего пакет и не допускает. Своя `padding-top` у шапки держит её
   содержимое ниже системного места при любой прокрутке и закрашивает это место
   своим фоном, так что текст не скользит под часами. Третье следствие не
   планировалось, но оказалось нужным: фон берётся у самой шапки, поэтому на экране
   переписки полоса остаётся цвета переписки, а не цвета списка.

   `top: 0` при этом НЕ меняется: трогать его значило бы снова разойтись с местом,
   которое шапка закрашивает (и заодно увести `top` у замеров D-274 из нуля в
   неразобранное `calc()`). Боковые и нижнее поля остаются на теле — там нет ничего
   липкого, и прижиматься им не к чему. */
body[data-fullscreen="yes"] { padding-top: 0; }
body[data-fullscreen="yes"] .top { padding-top: calc(var(--head-pad) + var(--safe-top)); }
.tabs { display: flex; flex-wrap: wrap; gap: 6px; }
.tab {
  flex: 1 1 auto; min-width: 64px; min-height: var(--tap);
  display: inline-flex; align-items: center; justify-content: center; gap: 4px;
  border: 0; border-radius: var(--r-def); background: var(--bg);
  color: var(--hint); font: inherit; font-size: 14px; cursor: pointer;
}
.tab[aria-pressed="true"] { background: var(--btn); color: var(--btn-fg); }
.tab__count { font-size: var(--meta); opacity: .8; }
.aa {
  min-height: var(--tap); padding: 0 10px; border: 0; border-radius: var(--r-def);
  background: var(--bg2); color: var(--fg); font: inherit; font-size: 14px; cursor: pointer;
}
/* ── поиск (П-57): поле в шапке списка, находки группами по чатам ── */
.find { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
.find__q {
  flex: 1 1 auto; min-width: 0; min-height: var(--tap); padding: 0 10px;
  border: 1px solid var(--borders); border-radius: var(--r-def);
  background: var(--bg); color: var(--fg); font: inherit; font-size: 15px;
}
.find__q::placeholder { color: var(--hint); }
.find__x, .find__all {
  flex: none; min-height: var(--tap); padding: 0 10px; border: 0;
  border-radius: var(--r-def); background: var(--bg2); color: var(--hint);
  font: inherit; font-size: 14px; cursor: pointer;
}
/* `hidden` обязан ПОБЕЖДАТЬ свой же `display`, иначе узел не скроется никогда:
   правило браузера `[hidden]{display:none}` слабее любого авторского `display` по
   происхождению, и ровно так кнопка «Показать ещё» не скрывалась у владельца (D-245).
   Правила по одному на селектор, а не списком через запятую: `.list[hidden]` — это
   класс плюс атрибут, то есть специфичность выше `.list`, и порядок уже не важен. */
.find__x[hidden] { display: none; }
.find__scope[hidden] { display: none; }
.list[hidden] { display: none; }
.find__scope {
  display: flex; align-items: center; gap: 8px; margin: 0 0 6px;
  font-size: 13px; color: var(--hint);
}
.sres__note { margin: 0; padding: 8px 12px; font-size: 13px; color: var(--hint); }
.sg {
  display: flex; align-items: center; gap: 8px; padding: 8px 12px 2px;
  font-size: 13px; color: var(--hint); position: sticky; top: 0; background: var(--bg2);
}
.sg__ava {
  flex: none; width: 22px; height: 22px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--own); color: #fff; font-size: 12px;
}
.sg[data-named="no"] .sg__ava { background: var(--bg); background-image: var(--hatch); }
.sg__name { flex: 1 1 auto; min-width: 0; font-weight: 500; color: var(--fg);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sg__at { flex: none; font-variant-numeric: tabular-nums; }
.sh {
  display: block; padding: 8px 12px; color: inherit; text-decoration: none;
  cursor: pointer; border-top: 1px solid var(--dividers);
}
.sh:hover { background: var(--bg); }
.sh__t { display: block; font-size: 15px; white-space: pre-wrap; overflow-wrap: anywhere; }
.sh__meta {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin-top: 3px;
  font-size: var(--meta); color: var(--hint);
}
.sh__who { font-weight: 500; }
.sh__at { font-variant-numeric: tabular-nums; }
.sh__at--seen { text-decoration: underline dotted; }
.sh__kind { display: inline-flex; color: var(--accent); }
.sh__m { padding: 0 6px; border-radius: var(--r-def); background: var(--bg2); }
.sh__m--sim { color: var(--accent); }
.sh__m--was { color: var(--btn-fg); background: var(--btn); }
.sh__m--del { color: var(--danger); }
.sh--del { background: #e6464612; }
.sh mark { background: var(--btn); color: var(--btn-fg); border-radius: 3px; padding: 0 1px; }
.honest { margin: 6px 12px; background: var(--bg2); border-radius: var(--r-def); }
.honest__summary {
  min-height: var(--tap); display: flex; align-items: center; padding: 0 10px;
  cursor: pointer; font-size: 14px; color: var(--hint);
}
.honest__line { margin: 0 10px 8px; font-size: 13px; color: var(--hint); }
.honest__line--empty { color: var(--fg); }
.m--found { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── список чатов: строка 72px, аватар 54px, отступ 9px (см. шапку раздела) ── */
.list { display: block; }
.chats__note { margin: 0; padding: 8px 12px; font-size: 13px; color: var(--hint); }
.ch {
  display: flex; align-items: center; gap: 8px;
  min-height: var(--row); padding: var(--pad) 12px;
  color: inherit; text-decoration: none; cursor: pointer;
}
.ch + .ch { border-top: 1px solid var(--dividers); }
.ch:hover, .ch[aria-current="true"] { background: var(--bg); }
.ch__ava {
  flex: none; width: var(--ava); height: var(--ava); border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--own); color: #fff; font-size: 22px; font-weight: 500;
}
.ch--noname .ch__ava { background: var(--bg); background-image: var(--hatch); }
.ch__mid { flex: 1 1 auto; min-width: 0; display: block; }
.ch__top { display: flex; align-items: baseline; gap: 8px; }
.ch__name {
  flex: 1 1 auto; min-width: 0; font-size: 16px; font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ch--noname .ch__name { color: var(--hint); font-variant-numeric: tabular-nums; }
.ch__at { flex: none; font-size: var(--meta); color: var(--hint); font-variant-numeric: tabular-nums; }
.ch__last {
  display: flex; align-items: center; gap: 4px; margin-top: 2px;
  font-size: 15px; color: var(--hint); overflow: hidden; white-space: nowrap;
}
.ch__text { overflow: hidden; text-overflow: ellipsis; }
.ch__text--none { opacity: .6; }
/* Пометка границы архива у группового чата (П-54c). `flex: none` — чтобы её не ужимал
   длинный текст последнего события: пометка, исчезающая на длинной подписи, молча
   отменяла бы требование ADR-0020 ровно в тех чатах, где подписи длинные. */
.ch__only { flex: none; font-size: 13px; color: var(--hint); opacity: .8; }
.ch__trash { flex: none; }
.ch__kind { flex: none; display: inline-flex; color: var(--accent); }
.ch__tally { flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 1px; }
.ch__del { font-size: 13px; font-weight: 500; color: var(--danger); font-variant-numeric: tabular-nums; }
.ch__del--all { white-space: nowrap; }
.ch__total { font-size: var(--meta); color: var(--hint); font-variant-numeric: tabular-nums; }
.ch--allgone { background: #e6464612; }

/* ── переписка ── */
.thread, .feed { display: flex; flex-direction: column; gap: 2px; padding: 8px 10px 24px; max-width: var(--col); margin: 0 auto; }
.th { padding: 8px 4px 12px; border-bottom: 1px solid var(--dividers); margin-bottom: 8px; }
.th__name { margin: 0; font-size: 17px; font-weight: 500; }
.th--noname .th__name { color: var(--hint); font-variant-numeric: tabular-nums; }
.th__tally { display: flex; flex-wrap: wrap; gap: 4px 10px; margin: 4px 0 0; font-size: 13px; color: var(--hint); }
.th__n--del { color: var(--danger); }
.th__n--ed { color: var(--warn); }
.th__line { margin: 6px 0 0; font-size: 13px; color: var(--hint); }
.th__line--allgone { color: var(--danger); }
.day { align-self: center; margin: 10px 0 6px; padding: 2px 10px; border-radius: var(--r-def); background: var(--bg2); color: var(--hint); font-size: var(--meta); }

/* Пузырь: БЕЗ обводки — только заливка и мягкая тень (решение владельца). Радиус 16px
   (их же переопределение для iOS), отступы iOS: 5px сверху, 8px по бокам, 7px снизу. */
.m { display: flex; max-width: 100%; }
.m--in { justify-content: flex-start; }
.m--out { justify-content: flex-end; }
.m__b {
  position: relative; max-width: min(29rem, 100% - 56px);
  padding: 5px 8px 7px; border: 0; border-radius: var(--r-msg);
  background: var(--bg2); box-shadow: 0 1px 4px 0 #0000000D;
  font-size: var(--msg-size); line-height: var(--msg-lh); overflow-wrap: anywhere;
}
.m--out .m__b { background: var(--own); max-width: min(30rem, 100% - 36px); }
.m--in + .m--in .m__b, .m--out + .m--out .m__b { border-top-left-radius: var(--r-msg-sm); }
.m--del .m__b { background: #3a2626; }
.m--del .m__b::before {
  content: ""; position: absolute; left: 0; top: 6px; bottom: 6px; width: 2px;
  border-radius: 2px; background: var(--danger);
}
.m--out.m--del .m__b::before { left: auto; right: 0; }
.m__t { white-space: pre-wrap; word-break: break-word; }
.m__i {
  display: inline-flex; align-items: center; gap: 5px; float: right;
  height: 20px; margin: 7px 0 0 7px; padding: 0 4px; border: 0;
  border-radius: var(--r-def); background: transparent; color: var(--hint);
  font: inherit; font-size: var(--meta); line-height: 1; cursor: pointer;
}
.m--out .m__i { color: #ffffffb3; }
.m__i:hover { background: #99999999; color: #fff; }
.m__at { font-variant-numeric: tabular-nums; }
/* Неточное время — иначе, чем точное (PRINCIPLES §22): пунктир под меткой. */
.m__at--seen { text-decoration: underline dotted; text-underline-offset: 2px; }
.m__e { color: var(--warn); }
.m--out .m__e { color: #ffe2a8; }
.m__d { color: var(--danger); white-space: nowrap; }
.m--out .m__d { color: #ffc9c9; }

/* ── вложения: элемент, а не подпись. Числа — из исходников (см. шапку раздела) ── */
.mx { position: relative; margin: 2px 0 4px; }
.mx__pill {
  display: inline-block; padding: 1px 6px; border-radius: var(--r-def);
  background: #00000066; color: #fff; font-size: var(--meta); white-space: nowrap;
}
.mx__dur {
  padding: 1px 6px; border-radius: var(--r-def); background: #00000066;
  color: #fff; font-size: var(--meta); font-variant-numeric: tabular-nums;
}
.mx__play {
  flex: none; width: var(--tap); height: var(--tap); border: 0; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent); color: #fff; cursor: pointer;
}
.mx__play[disabled] { opacity: .55; cursor: default; }
.mx__play svg { fill: currentColor; }
.ic { fill: currentColor; }
/* дорожка голосового: 30 столбиков, шаг 4px, высота 23px — waveform.ts */
.mx--wave, .mx--track { display: flex; align-items: center; gap: 8px; min-width: 17rem; }
.mx__wave { flex: none; }
.mx__bars { fill: var(--hint); }
.mx--wave[data-waveform="unknown"] .mx__bars { fill: #ffffff33; }
.mx--wave[data-waveform="unknown"] .mx__wave { background-image: var(--hatch); border-radius: 3px; }
.mx__track-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.mx__name, .mx__by, .mx__size { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mx__name { font-size: 14px; }
.mx__by, .mx__size { font-size: 13px; color: var(--hint); }
.mx [data-name="unknown"], .mx [data-performer="unknown"], .mx [data-size="unknown"] { color: var(--hint); font-style: italic; }
/* кружок: 240px, круглая обрезка. В ПОКОЕ ни кольца, ни кнопки — их нет и у Telegram. */
.mx--round { width: 240px; max-width: 100%; aspect-ratio: 1; margin: 2px 0 6px; display: block; }
.mx__disc { width: 100%; height: auto; display: block; }
.mx__hatch { fill: #2b2b2b; }
.mx--round .mx__dur { position: absolute; left: 50%; bottom: 8px; transform: translateX(-50%); }
.mx--round .mx__mute { position: absolute; right: 16%; bottom: 10px; font-size: 16px; margin: 6px; color: #fff; opacity: .8; }
.mx--round .mx__pill { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); }
.mx--round, .mx--frame { background-image: var(--hatch); border-radius: var(--r-def); }
.mx--round { border-radius: 50%; }
/* кадр фото/видео/гифки — заполнится превью в П-51 */
.mx--frame { width: 240px; max-width: 100%; display: block; overflow: hidden; }
.mx__frame { width: 100%; height: auto; display: block; }
.mx--frame .mx__hatch { fill: #00000000; }
.mx__glyph { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); color: #ffffff5c; }
.mx--frame .mx__pill { position: absolute; left: 6px; bottom: 6px; }
.mx--frame .mx__dur { position: absolute; right: 6px; bottom: 6px; }
.mx__play--over { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 48px; height: 48px; }
.mx__fmt { position: absolute; right: 6px; top: 6px; padding: 1px 5px; border-radius: 4px; background: #00000066; color: #fff; font-size: 11px; font-weight: 500; }
/* плашка файла — имя и размер заполнит П-51 */
.mx--file { display: grid; grid-template-columns: 44px 1fr; grid-template-rows: auto auto auto; gap: 1px 8px; align-items: center; padding: 6px 2px; }
.mx__fileic { grid-row: 1 / span 3; fill: var(--accent); }
.mx--other { display: block; width: 160px; max-width: 100%; }

/* ── полоса простоя: видимый текст КОРОТКИЙ, полный — в data-words ── */
.band {
  align-self: center; max-width: 100%; margin: 6px 0; padding: 3px 10px;
  border-radius: var(--r-def); font-size: var(--meta); cursor: pointer;
}
.band--gap { background: #4a1f1f; color: #ffd4d4; }
.band--thin { background: transparent; color: var(--hint); }
.band__label { font-weight: 500; }
.band--thin .band__label { font-weight: 400; }
.band__full { display: block; margin-top: 2px; font-weight: 400; opacity: .9; }

/* ── лента: тот же пузырь плюс подпись «кто и где» ──
   В интерфейсе ленты нет (решение владельца), но маршрут `/api/feed` жив и отдаёт эту
   разметку. Стили остаются: снять их значит оставить живой маршрут без оформления, то
   есть сломать его молча — а возврат переключателя стоит одной кнопки. */
.fr { display: flex; flex-direction: column; margin-bottom: 6px; }
.fr__who { display: flex; flex-wrap: wrap; gap: 2px 8px; font-size: var(--meta); color: var(--hint); padding: 0 4px 2px; }
.fr__person { font-weight: 500; color: var(--fg); }
.fr__date { font-variant-numeric: tabular-nums; }

/* ── карточка ── */
.card {
  width: min(560px, 94vw); max-height: 86vh; padding: 0; border: 0;
  border-radius: var(--r-def); background: var(--bg2); color: var(--fg);
  overflow: auto;
}
.card::backdrop { background: #000000a6; }
.card__x {
  position: sticky; top: 0; float: right; width: 36px; height: 36px; border: 0;
  background: transparent; color: var(--hint); font-size: 22px; cursor: pointer;
}
.card__body { padding: 12px 14px 16px; }
.cd__title { margin: 0 0 8px; font-size: 16px; font-weight: 500; }
.cd__facts { display: grid; grid-template-columns: auto 1fr; gap: 4px 10px; margin: 0; font-size: 14px; }
.cd__facts dt { color: var(--hint); }
.cd__facts dd { margin: 0; grid-column: 2; overflow-wrap: anywhere; }
.cd__facts dd.cd__unknown, .cd__facts dd.cd__gap { grid-column: 1 / -1; }
.cd__unknown { margin-top: 2px; padding: 6px 8px; border-radius: var(--r-def); background: var(--bg); color: var(--hint); }
.cd__gap { margin-top: 2px; padding: 6px 8px; border-radius: var(--r-def); background: #4a1f1f; color: #ffd4d4; }
.cd__gap-note { margin: 4px 0 0; }
.cd__was, .cd__now { margin: 6px 0 0; padding: 5px 8px; border-left: 2px solid var(--hint); border-radius: 0 var(--r-def) var(--r-def) 0; background: var(--bg); white-space: pre-wrap; }
.cd__now { border-left-color: var(--accent); }
.cd__was-label { display: block; font-size: var(--meta); color: var(--hint); }
.cd__none { margin: 6px 0 0; font-size: 13px; color: var(--hint); }
/* «замечено» у правки: второй момент, и он приглушён — главный момент это сама
   правка, а не наш приём (см. SEEN_APART_SECONDS). */
.cd__seen { margin: 2px 0 0; font-size: 13px; color: var(--hint); }
.cd__media { color: var(--hint); }
.cd__ids { margin: 10px 0 0; font-size: var(--meta); color: var(--hint); font-variant-numeric: tabular-nums; }

/* Ход обратно к свежим после перехода из поиска. Плавающая, у нижнего края — там же,
   где этот ход живёт в оригинале (iOS). Прежний довод «в шапке её не видно, потому что
   шапка наверху не держится» СНЯТ в П-54c: шапки держатся (см. два объявления
   `overflow-x` выше). Место кнопки не изменилось по другой причине — так её ждёт палец.
   Отступ снизу считает безопасную зону: под ней на iPhone живёт системная полоса. */
.jump {
  position: fixed; right: 12px; z-index: 4;
  /* Поле — из общей переменной (`:root`), а не своим выражением: кнопка прибита к
     ВЬЮПОРТУ, набивка тела её не касается, а в полном экране снизу прибавляется
     место системной полосы — и оба состояния должны считаться одним источником. */
  bottom: calc(16px + var(--safe-bottom));
  width: 48px; height: 48px; border: 0; border-radius: 50%;
  background: var(--bg2); color: var(--fg); font-size: 20px; line-height: 1;
  box-shadow: 0 2px 8px #00000066; cursor: pointer;
}

/* ── состояния и догрузка ── */
.empty, .state { padding: 10px 12px; color: var(--hint); font-size: 13px; }
.state--error { color: var(--danger); }
.more {
  display: block; margin: 8px auto 24px; min-height: var(--tap); padding: 0 16px;
  border: 0; border-radius: var(--r-def); background: var(--btn); color: var(--btn-fg);
  font: inherit; cursor: pointer;
}

/* Широкий экран — две колонки: список слева, переписка справа. Те же данные и тот же
   код, различие только здесь (правая колонка 26.5rem у них; у нас список — боковой). */
@media (min-width: 51.25em) {
  .app { display: flex; align-items: stretch; }
  .pane--list { flex: 0 0 var(--side); max-width: var(--side); border-right: 1px solid var(--borders); }
  .pane--thread { flex: 1 1 auto; }
  /* Обе колонки сразу: список слева, переписка справа — те же данные и тот же код. */
  body[data-thread="open"] .pane--list { display: block; }
  body:not([data-thread="open"]) .pane--thread { display: block; }
}
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

.more[hidden] { display: none; }
.jump[hidden] { display: none; }
/* ── Почему пять правил выше стоят последними и каждое отдельной строкой ──
   Скрипт прячет узлы атрибутом hidden, а почти у каждого из них есть своё правило
   display выше. Правило БРАУЗЕРА для этого атрибута слабее любого авторского display —
   не по специфичности, а по происхождению; именно так кнопка «Показать ещё» не
   скрывалась никогда (D-245). Значит своё правило обязательно.
   Последними — чтобы оно побеждало и по порядку, а не только по специфичности: иначе
   следующая правка добавит второй класс, и спрятанное перестанет прятаться МОЛЧА.
   Каждое отдельной строкой, а не одним списком селекторов: у списка нет одной
   специфичности, и ни читатель, ни разбор не скажут, какой узел он кроет.
   Комментарий стоит ПОСЛЕ правил, а не перед ними, и это тоже не стиль: разбор стилей в
   проверке приклеивает предшествующий комментарий к селектору, и объяснение перед
   правилом сделало бы правило невидимым для неё. */
