/* =========================================================================
   chat-ui.css — слой оформления страницы chat.php.

   Почему отдельный файл, а не правки в style.css: style.css общий для всех
   страниц сайта (профиль, посты, настройки, игры), и переопределение базовых
   значений в нём задело бы их все. Здесь собрано только то, что относится
   к хабу и к странице диалога, и подключается этот файл строго после
   style.css — каскад даёт нужный приоритет без гонки !important.

   Базовые шкалы (радиусы, тени, кегли, цвета) лежат НЕ здесь, а в style.css:
   ими пользуются и общий чат, и посты, и профиль. См. раздел 1.

   Структура:
     1. Токены — переехали в style.css, здесь только ссылка
     2. Базовая доступность (фокус, тач-таргеты, движение)
     3. Хаб: панель навигации и вкладки
     4. Хаб: сетка колонок
     5. Хаб: карточки колонок
     6. Лента активности
     7. Блок «Общий чат: последние сообщения»
     8. Гостевой режим
     9. Страница диалога
    10. Мобильная нижняя навигация
   ========================================================================= */

/* ---------- 1. Токены ---------------------------------------------------- */

/* Базовые шкалы — радиусы, тени, кегли, цвета — переехали в style.css,
   в блоки :root и .dark-theme. Здесь их держать было нельзя: этот файл
   подключается только к chat.php, а те же токены нужны общему чату, постам
   и профилю. Именно из-за раздельного хранения правки общих компонентов
   трижды доставались одной странице из двух.
   Ниже — только то, что относится к самой странице чата. */

/* ---------- 2. Базовая доступность --------------------------------------- */

/* Кольцо фокуса переехало в style.css и действует теперь на всех страницах,
   а не только на чате — см. блок в конце того файла. Здесь оно было заведено
   первым и потому долго оставалось единственным на сайте.

   На заметку тому, кто будет его править: border-radius в правило фокуса
   добавлять нельзя. Он меняет скругление самого элемента, а не обводки, —
   поле поиска в диалоге «пилюля», и при фокусе оно превращалось в
   прямоугольник. Обводка и без того повторяет форму элемента. */

/* Иконочные кнопки были 20–23px — ниже минимума 24×24 из WCAG 2.2 (2.5.8).
   Увеличиваем сами кнопки, а не подкладываем невидимую зону нажатия:
   расширенная область у .hide-content-btn или у кнопок ленты легла бы поверх
   соседнего текста и ссылок и перехватывала бы клики по ним. */
/* 30px, а не 28: столько же у кнопки обновления и шестерёнки в колонке
   «Сейчас обсуждают». Кнопки соседних колонок стоят в одинаковых шапках, и
   разница в пару пикселей была заметна именно на соседстве. */
.ulist-quest,
.dialogs-settings-btn,
.ulist-toggle-btn {
    width: 30px;
    height: 30px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--r-full);
    transition: background var(--dur) var(--ease), opacity var(--dur) var(--ease);
}

/* У кнопки справки значок задан фоном с background-size:contain — без явного
   размера он растянулся бы вслед за увеличенной кнопкой. Растёт зона нажатия,
   а сам знак вопроса остаётся прежним. */
.ulist-quest {
    background-size: 18px 18px;
}

@media (hover: hover) {
    .ulist-quest:hover,
    .dialogs-settings-btn:hover,
    .ulist-toggle-btn:hover {
        background-color: var(--surface-hover);
    }
}

/* Свёрнутая лента высотой 30px, кнопки отступают сверху на 4px — значит
   в неё помещается ровно 22px. При 26px они упирались в нижнюю границу. */
.activity-feed-close-btn,
.activity-feed-expand-btn {
    width: 22px;
    height: 22px;
    top: 4px;
}

.hide-content-btn {
    min-width: 28px;
    min-height: 28px;
    border-radius: var(--r-full);
}

@media (prefers-reduced-motion: reduce) {
    .hub-tabs__ink,
    .overall-live__item,
    .post-item,
    .comment-item {
        transition: none !important;
        animation: none !important;
    }
}

/* «Фонарик» на тач-устройствах.
   Подсветка граней завязана на :hover, а на сенсорном экране :hover после
   касания залипает до следующего тапа в другом месте — подсвеченная карточка
   так и оставалась гореть. Гасим hover-состояние там, где настоящего курсора
   нет, и вешаем свечение на :active: оно загорается под пальцем и плавно
   уходит на отпускании за счёт уже заданного transition:opacity.
   Список селекторов повторяет правило в style.css — специфичность должна
   совпасть, иначе оно не перебьётся; своя строка в chat_main.js держит тот же
   набор элементов. */
@media (hover: none), (pointer: coarse) {
    .header:hover .mouse-glare,
    .footer:hover .mouse-glare,
    .user-item:hover .mouse-glare,
    .post-item:hover .mouse-glare,
    .comment-item:hover .mouse-glare,
    .overall-live__item:hover .mouse-glare,
    .dialogs-fab-btn:hover .mouse-glare,
    .user-column:hover .mouse-glare,
    .posts-column:hover .mouse-glare,
    .comments-column:hover .mouse-glare,
    .overall-column:hover .mouse-glare,
    .chat-box:hover .mouse-glare,
    .message-bubble:hover .mouse-glare,
    .message-input-container:hover .mouse-glare,
    .activity-feed-container:hover .mouse-glare,
    .comments-toggle-btn:hover .mouse-glare {
        opacity: 0;
    }

    .user-item:active .mouse-glare,
    .post-item:active .mouse-glare,
    .comment-item:active .mouse-glare,
    .overall-live__item:active .mouse-glare,
    .dialogs-fab-btn:active .mouse-glare,
    .comments-toggle-btn:active .mouse-glare {
        opacity: 1;
    }

    /* Чуть медленнее гаснет, чем появляется, — иначе на отпускании эффект
       обрывается и выглядит как мигание. */
    .mouse-glare {
        transition: opacity .45s ease;
    }
}

/* Принудительное гашение подсветки после касания. Класс вешает chat_main.js
   через 400 мс после touchend; !important здесь необходим — правила вида
   `.post-item:hover .mouse-glare{opacity:1}` состоят из трёх классов и иначе
   перевесят. Гасим именно прозрачностью: у неё есть переход, и грани уходят
   плавно, тогда как смещение центра градиента срабатывало бы рывком. */
.mouse-glare.glare-off {
    opacity: 0 !important;
}

/* Радужная обводка на каждой колонке, карточке и пузыре в тёмной теме
   читалась как незаконченный эффект и стоила композитинга на десятках
   элементов при каждой загрузке. Оставляем её только там, где она
   действительно к месту — на плавающей кнопке диалогов. */
.dark-theme .user-column::after,
.dark-theme .posts-column::after,
.dark-theme .comments-column::after,
.dark-theme .overall-column::after,
.dark-theme .chat-box::after,
.dark-theme .message-input-container::after,
.dark-theme .activity-feed-container::after,
.dark-theme .comments-toggle-btn::after {
    display: none;
}

/* ---------- 3. Хаб: панель навигации ------------------------------------- */

.chat-btns {
    gap: 8px;
    padding: 10px 0 12px;
    flex-wrap: wrap;
}

/* Кнопка возврата скрытой ленты стоит первой в ряду разделов и оформлена под
   соседние кнопки: тот же фон, рамка, скругление и высота, отличается только
   отсутствием подписи. Механику появления (max-width 0 → 36px плюс отскок)
   задаёт style.css и chat_main.js — её не трогаем, меняем лишь оформление. */
/* Отрицательный правый отступ в скрытом состоянии — не украшение, а
   компенсация. Кнопка прячется анимацией max-width 36px → 0, но нулевой
   flex-элемент всё равно съедает зазор ряда (gap: 8px) и собственные рамки
   (2px при box-sizing: border-box). Пока она в потоке, эти 10px держат
   соседние кнопки сдвинутыми, а когда скрипт в самом конце ставит
   display:none, они исчезают разом — отсюда рывок в конце анимации.
   Отступ анимируется вместе с шириной (transition задан в style.css), поэтому
   к моменту display:none вклад кнопки в раскладку уже ровно нулевой.
   Это не изобретение: в style.css у кнопки исходно стоял margin-right:-10px,
   и я потерял его, обнулив margin целиком ради вертикального выравнивания. */
.chat-btns .activity-feed-toggle-btn {
    margin: 0 -10px 0 0;
    height: 36px;
    background-color: var(--col-bg);
    color: var(--brand);
    border: 1px solid var(--hairline);
    box-shadow: var(--e0);
    border-radius: var(--r-full);
    box-sizing: border-box;
}

/* Показанной кнопке компенсация не нужна — она занимает место по-настоящему.
   Класса тут два, и оба обязательны. При появлении скрипт сначала вешает
   feed-btn-pop (анимация отскока, ~0.4с) и только по её окончании меняет его
   на feed-btn-visible. В style.css margin-right:0 задан для обоих состояний,
   но правило выше, с .chat-btns, перевешивает их по специфичности — без
   упоминания feed-btn-pop отступ всё время отскока оставался бы
   компенсирующим и появлялся бы рывком уже после анимации. */
.chat-btns .activity-feed-toggle-btn.feed-btn-visible,
.chat-btns .activity-feed-toggle-btn.feed-btn-pop {
    margin-right: 0;
}

/* Непрозрачность — только у конечного состояния: во время отскока ею
   управляют кадры анимации. */
.chat-btns .activity-feed-toggle-btn.feed-btn-visible {
    opacity: 1;
}

@media (hover: hover) {
    .chat-btns .activity-feed-toggle-btn:hover {
        background-color: var(--brand-soft);
        border-color: var(--brand);
    }
}

/* Точка непрочитанного в ленте сидела на белой обводке — на светлой кнопке
   её теперь не видно, привязываем цвет к фону кнопки. */
.chat-btns .feed-unread-badge {
    border-color: var(--col-bg);
}

.chat-btns .all-users-link.overallchat-btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin: 0;
    padding: 0 16px;
    /* Фиксированная высота, а не вертикальные отступы: рядом стоит кнопка
       возврата ленты, у которой высота задана жёстко механикой её появления,
       и подгонять их друг под друга через padding пришлось бы на каждом
       изменении шрифта. */
    min-height: 36px;
    font-size: var(--fs-md);
    font-weight: 600;
    line-height: 1;
    border-radius: var(--r-full);
    background: var(--col-bg);
    color: var(--text);
    border: 1px solid var(--hairline);
    box-shadow: var(--e0);
    opacity: 1;
    transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
                transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}

/* Селектор выше состоит из трёх классов и потому перебивал .hidden-default
   (один класс) — скрытая кнопка «Диалоги» из-за этого проявилась в ряду
   разделов. Возвращаем ей скрытость: ту же панель открывает плавающая кнопка
   в правом нижнем углу, и две одинаковые точки входа рядом только мешают. */
.chat-btns .all-users-link.overallchat-btn.hidden-default {
    display: none;
}

.chat-btns .all-users-link.overallchat-btn .btn-ico {
    color: var(--brand);
    flex-shrink: 0;
}

@media (hover: hover) {
    .chat-btns .all-users-link.overallchat-btn:hover {
        background: var(--brand-soft);
        border-color: var(--brand);
        transform: translateY(-1px);
        box-shadow: var(--e1);
    }
}

.chat-btns .all-users-link.overallchat-btn:active {
    transform: translateY(0) scale(.97);
}

/* Бейджи непрочитанного — один вид на все точки входа. Размеры здесь общие,
   и менять их ради одной точки входа нельзя: правило обслуживает и счётчик у
   кнопки «Общий чат», и его гостевой вариант. Отличия плавающей кнопки
   диалогов — отдельным правилом ниже. */
.unread-overall-badge,
.guest-unread-overall-badge,
.dialogs-fab-badge {
    background: var(--alert-solid);
    color: #fff;
    font-size: var(--fs-xs);
    font-weight: 700;
    line-height: 18px;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: var(--r-full);
    text-align: center;
    box-sizing: border-box;
    box-shadow: 0 0 0 2px var(--page-bg);
}

/* Счётчик на плавающей кнопке диалогов — крупнее остальных, и это оправдано
   разницей носителя: он сидит на кружке диаметром 56px, где бейдж в 18px
   читается как точка, а не как число. Счётчики в панели навигации стоят
   вплотную к тексту кнопки, там увеличение только ломало бы строку.

   Обводка при этом ОДНА. У этого бейджа в style.css есть своя рамка
   border:2px solid #fff, и вместе с кольцом-тенью получался ободок в четыре
   пикселя — цифра тонула в белом. Рамку снимаем, кольцо оставляем тонким:
   его задача отделить бейдж от кнопки, а не обвести его. */
.dialogs-fab-badge {
    font-size: var(--fs-sm);
    line-height: 21px;
    min-width: 21px;
    height: 21px;
    padding: 0 6px;
    border: none;
    box-shadow: 0 0 0 1.5px var(--page-bg);
}

/* ---------- Вкладки (мобильный хаб) -------------------------------------- */

/* На узком экране три колонки раньше просто вставали друг под друга: до
   комментариев было 3–5 экранов скролла, и внутри каждой колонки работал
   собственный overflow — палец попадал не в тот контейнер. Вместо стека —
   переключатель разделов. */
.hub-tabs {
    display: none;
    position: relative;
    gap: 2px;
    margin: 0 0 12px;
    padding: 3px;
    background: var(--col-bg);
    border: 1px solid var(--hairline);
    border-radius: var(--r-full);
    box-shadow: var(--e0);
    overflow: hidden;
}

.hub-tabs__ink {
    position: absolute;
    top: 3px;
    bottom: 3px;
    left: 0;
    width: 0;
    border-radius: var(--r-full);
    /* Мягкая заливка вместо сплошного фирменного цвета: сплошной пятном
       перетягивал на себя внимание с содержимого раздела. */
    background: var(--brand-fill);
    transition: transform var(--dur) var(--ease), width var(--dur) var(--ease);
    pointer-events: none;
    z-index: 0;
}

.hub-tab {
    position: relative;
    z-index: 1;
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 34px;
    padding: 0 4px;
    border: none;
    background: transparent;
    color: var(--text);
    font-size: var(--fs-sm);
    font-weight: 600;
    font-family: inherit;
    line-height: 1.1;
    cursor: pointer;
    border-radius: var(--r-full);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    transition: color var(--dur) var(--ease);
    -webkit-tap-highlight-color: transparent;
}

.hub-tab[aria-selected="true"] {
    color: var(--brand);
}

/* Точка непрочитанного не должна занимать место, пока её не видно: раньше она
   была обычным flex-элементом и, даже прозрачная, съедала свои 7px плюс gap —
   из-за чего подпись КАЖДОЙ вкладки уезжала влево от центра. Поэтому у
   неактивной точки нулевая ширина, а отступ появляется вместе с ней; gap у
   .hub-tab по той же причине не используется — он применился бы и к элементу
   нулевой ширины. */
.hub-tab__dot {
    width: 0;
    height: 6px;
    border-radius: var(--r-full);
    background: var(--alert-solid);
    flex-shrink: 0;
    opacity: 0;
    transition: opacity var(--dur) var(--ease), width var(--dur) var(--ease),
                margin-left var(--dur) var(--ease);
}

.hub-tab__dot.active {
    width: 6px;
    margin-left: 5px;
    opacity: 1;
}

/* ---------- 4. Хаб: сетка колонок ---------------------------------------- */

/* ВАЖНО про высоту. .cont-chat задан height:calc(100dvh - 150px) и
   flex-direction:column, а .main-layout — его flex-элемент: он сжимается до
   оставшегося места, отсюда колонки получают конечную высоту, и уже внутри них
   прокручиваются .user-list / .posts-list / .comments-list. Страница при этом
   не скроллится, а .footer (position:fixed) стоит внизу экрана.
   Эту цепочку легко порвать: display:grid с align-items:start заставляет
   колонки расти по содержимому, сетка перестаёт помещаться в отведённую
   высоту, страница становится длинной — и фиксированный футер повисает
   посреди неё. Поэтому в ряду из четырёх колонок раскладка остаётся flex,
   а пропорции задаются через flex-grow.

   Оговорка к сказанному: в планшетном диапазоне (медиазапрос ниже) раскладка
   всё-таки сетка — но там строки заданы явно, repeat(2, minmax(0, 1fr)), то
   есть высота делится поровну независимо от содержимого. Предупреждение выше
   относится к сетке, которая размеряется содержимым; такой сетке здесь места
   нет ни в каком диапазоне. */
.main-layout {
    display: flex;
    gap: 18px;
    min-height: 0;
}

/* Список участников — самая насыщенная колонка, ему чуть больше места.
   flex-basis:0 обязателен: с basis:auto доли считались бы от объёма
   содержимого, и колонка с длинными сообщениями перетягивала бы одеяло. */
.column {
    --col-r: var(--r-lg);
    flex: 1 1 0;
    min-width: 0;
    /* border-box обязателен, а не вкусовщина: глобального сброса в проекте нет,
       и по умолчанию рамка в 1px прибавляется К заданной ширине. Из-за этого
       двухколоночная раскладка (flex-basis:calc(50% - 9px) ниже) никогда не
       работала: две колонки с рамками давали 100% + 4px, строка переполнялась,
       и вторая колонка переносилась вниз — получалась лента из четырёх колонок
       во всю ширину. */
    box-sizing: border-box;
    background: var(--col-bg);
    border: 1px solid var(--hairline);
    border-radius: var(--col-r);
    box-shadow: var(--e1);
    /* Обрезки по контуру здесь быть не должно ни в каком виде.
       overflow:hidden сделал бы колонку контейнером прокрутки, и sticky у
       заголовка перестал бы работать; overflow:clip этого не делает, но всё
       равно срезает то, что выходит за границы, — а именно там живёт
       «фонарик»: у .mouse-glare::before задан inset:-1px, и свечение граней
       рисуется ровно по кромке. Поэтому вместо обрезки скругляем углы у самих
       крайних полос — шапки и подвала колонки (см. ниже). */
}

/* Верхняя и нижняя полосы повторяют скругление колонки, чтобы их сплошной фон
   не выглядывал квадратными углами из-под скруглённой рамки. */
.ulist-head,
.posts-column > h3,
.comments-column > h3,
.overall-column > .column-head {
    border-radius: var(--col-r) var(--col-r) 0 0;
}

.all-users {
    border-radius: 0 0 var(--col-r) var(--col-r);
}

.user-column {
    flex-grow: 1.05;
}

/* Планшетный диапазон: четыре колонки в ряд ужимаются до ~180px и содержимое
   в них рассыпается, поэтому от 851 до 1366px раскладка идёт в две колонки.

   ВНИМАНИЕ, здесь раньше стояло обратное решение и обратный вывод. Прежний
   комментарий утверждал, что две строки колонок поделят экран пополам, ячейка
   выйдет около 250px, и это хуже, чем страничная прокрутка, — поэтому диапазон
   переводился в «телефонную» модель: высота по содержимому, прокрутка
   страницы, футер откреплён.

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

   Сейчас в этом диапазоне работает та же модель, что и при четырёх колонках:
   высоту держит цепочка .cont-chat → .main-layout, внутри колонок
   прокручиваются списки, страница стоит на месте, футер закреплён. Плата за
   это ровно та, о которой предупреждал прежний комментарий: на невысоком
   экране каждая из четырёх ячеек занимает около половины доступной высоты.
   Если это окажется тесно, правильная ручка — не возврат к странице, а
   высота .cont-chat в этом диапазоне. */
@media (min-width: 851px) and (max-width: 1366px) {
    /* Сетка 2×2, а НЕ flex-wrap — и это принципиально.

       Обёртка (flex-wrap:wrap) две строки даёт, но высоту им не задаёт:
       строка во flex сжимается по своему содержимому, а align-content
       распределяет только СВОБОДНОЕ место. Если содержимое выше половины
       контейнера — а список участников выше всегда, — строки просто вылезают
       наружу, страница снова тянется на несколько экранов, и закреплённый
       футер повисает посреди неё. Именно это и получилось при первой попытке.

       Сетка задаёт высоту строк явно: две по 1fr делят доступную высоту
       поровну независимо от содержимого. minmax(0, 1fr) вместо 1fr — чтобы
       длинный список не распирал ячейку изнутри (у ячеек минимальный размер
       по содержимому, и без обнуления он побеждает).

       Дальше работает обычная цепочка: .cont-chat задаёт высоту, .main-layout
       сжимается до неё, колонки получают конечную высоту ячейки, а внутри
       прокручиваются .user-list / .posts-list / .comments-list. */
    .main-layout {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        grid-template-rows: repeat(2, minmax(0, 1fr));
    }

    /* Ячейка сетки, как и флекс-элемент, по умолчанию не сжимается меньше
       своего содержимого. Без обнуления колонка распирала бы строку, и весь
       расчёт выше терял бы смысл. */
    .column {
        min-height: 0;
    }
}

/* Вкладки — до 850px включительно.

   История порога: сначала он стоял на 850, потом был опущен до 600 ради
   двухколоночной раскладки на планшете. Но между 601 и 850 двух колонок не
   получалось: в style.css для этого же диапазона висело
   .main-layout{flex-direction:column} с .column{min-width:100%}, и колонки
   шли одна под другой во всю ширину — бесконечной лентой без всякой
   навигации. То есть выигранный диапазон работал хуже вкладок.

   Теперь три состояния без промежуточных: до 850 — вкладки, 851–1366 — две
   колонки в ряд, дальше — четыре. Раскладки «одна колонка на всю ширину»
   не существует ни при какой ширине. */
@media (max-width: 850px) {
    .main-layout {
        display: block;
        gap: 0;
    }

    /* Явная высота нужна для отступа закреплённого заголовка колонки ниже:
       он должен встать вплотную под полосой вкладок, иначе между ними
       остаётся щель, сквозь которую видно прокручивающееся содержимое.
       3px поля сверху и снизу + 34px кнопка + 1px рамки = 42px. */
    .hub-tabs {
        display: flex;
        height: 42px;
        box-sizing: border-box;
    }

    /* Показываем ровно одну панель. hidden не используем: у .column задан
       display, и атрибут пришлось бы перебивать !important. */
    .main-layout > .column {
        display: none;
    }

    .main-layout[data-tab="users"] > .user-column,
    .main-layout[data-tab="overall"] > .overall-column,
    .main-layout[data-tab="posts"] > .posts-column,
    .main-layout[data-tab="comments"] > .comments-column {
        display: flex;
    }

    /* Въезд раздела при переключении свайпом. Направление задаёт data-swipe:
       палец ушёл влево — следующий раздел приходит справа, и наоборот. Без
       этого при листании пальцем содержимое подменяется мгновенно, и жест
       читается как сбой, а не как переход.

       Анимация висит на показанной колонке: она заново проигрывается каждый
       раз, когда элемент возвращается из display:none. По кнопкам вкладок
       атрибут не ставится — там подмена мгновенная, как и была. */
    .main-layout[data-swipe="next"] > .column,
    .main-layout[data-swipe="prev"] > .column {
        animation: hub-panel-in .22s var(--ease, ease) both;
    }

    .main-layout[data-swipe="prev"] > .column {
        animation-name: hub-panel-in-back;
    }

    @keyframes hub-panel-in {
        from { opacity: 0; transform: translateX(14px); }
        to   { opacity: 1; transform: none; }
    }

    @keyframes hub-panel-in-back {
        from { opacity: 0; transform: translateX(-14px); }
        to   { opacity: 1; transform: none; }
    }

    @media (prefers-reduced-motion: reduce) {
        .main-layout[data-swipe] > .column { animation: none; }
    }

    /* Вложенный скролл внутри страницы убираем — скроллит страница целиком. */
    .posts-column,
    .comments-column,
    .user-column,
    .overall-column {
        max-height: none;
    }

    .user-list,
    .posts-list,
    .comments-list,
    .overall-live {
        overflow: visible;
        max-height: none;
    }

    /* Исключение — список с заглушками. Своей высоты у колонки здесь нет
       (прокручивается страница), обрезать запас карточек нечем, и заглушка
       растянула бы страницу на несколько экранов. Ограничиваем её примерно
       экраном: столько и видно, а остальное всё равно сменится настоящими
       карточками. Правило перекрывает соседнее по специфичности — у него
       два класса против одного. */
    .user-list.is-skeleton {
        overflow: hidden;
        max-height: 75vh;
    }
}

/* Шапки всех четырёх колонок одной высоты. Раньше высота набиралась
   вертикальными отступами заголовка, и там, где рядом с названием стоит
   счётчик, строка становилась выше самого счётчика — шапки «Участники» и
   «Последние посты» оказывались заметно выше соседних. Теперь высоту задаёт
   min-height, содержимое центрируется по вертикали, а собственных
   вертикальных отступов у заголовка нет — размер счётчика на высоту больше
   не влияет. min-height, а не height: в узком диапазоне 850–1024px
   .ulist-header-content выстраивается в столбец и шапка обязана вырасти. */
.ulist-head,
.posts-column > h3,
.comments-column > h3,
.overall-column > .column-head {
    display: flex;
    align-items: center;
    min-height: 46px;
    box-sizing: border-box;
    background: var(--col-bg);
    border-bottom: 1px solid var(--hairline);
}

.ulist-head > .ulist-header-content {
    flex: 1 1 auto;
    min-width: 0;
}

/* Закрепляем заголовок раздела и полосу вкладок только там, где это что-то
   даёт — на узком экране, где скроллит вся страница и вкладки существуют.
   Заголовок встаёт РОВНО под полосой вкладок, иначе между ними остаётся щель,
   сквозь которую видно прокручивающееся содержимое. Высоту полосы измеряет и
   кладёт в --hub-tabs-h chat-hub.js: жёсткое значение в стилях расходилось с
   фактическим на доли пикселя, а этого уже достаточно для щели. 42px —
   запасное значение на случай, если скрипт не загрузился. */
@media (max-width: 850px) {
    .hub-tabs {
        position: sticky;
        top: 0;
        z-index: 6;
    }

    .ulist-head,
    .posts-column > h3,
    .comments-column > h3,
    .overall-column > .column-head {
        position: sticky;
        /* -1px — перекрытие в пиксель: при дробном масштабировании страницы
           округление высоты полосы и позиции заголовка может разойтись, и
           граница снова просвечивает. Перекрытие такую разницу съедает. */
        top: calc(var(--hub-tabs-h, 42px) - 1px);
        z-index: 4;
    }
}

/* Вертикальных отступов нет намеренно — высоту шапки держит min-height выше.
   Стоит вернуть сюда padding-top/bottom, и шапки со счётчиками снова станут
   выше остальных. */
.ulist-head h3,
.posts-column h3,
.comments-column h3,
.overall-column .column-head h3 {
    margin: 0;
    padding: 0 12px;
    font-size: var(--fs-lg);
    font-weight: 700;
    letter-spacing: -.01em;
    gap: 8px;
}

/* Шапка списка участников — единственная с боковыми кнопками, и раньше они
   выравнивались вручную: у .ulist-controls-left стоял position:relative; top:7px,
   у правой группы top:5px. Эти сдвиги были подогнаны под прежние отступы
   заголовка (margin:15px 10px 5px); после смены отступов они сместили кнопки.
   Убираем подгонку и выравниваем всё честным flex-ом: отступы переезжают на
   контейнер, заголовок остаётся без собственных полей. */
.ulist-header-content {
    padding: 4px 12px;
    gap: 8px;
    justify-content: space-between;
    /* Колонка стала уже (их теперь четыре), поэтому при совсем узком заголовке
       разрешаем перенос — иначе кнопки выдавливали бы название за край. */
    flex-wrap: wrap;
}

.ulist-controls-left,
.ulist-controls-right {
    top: 0;
    display: flex;
    align-items: center;
    flex-shrink: 0;
}

.ulist-title-wrap {
    flex: 1 1 auto;
    display: flex;
    justify-content: center;
    align-items: center;
    min-width: 0;
}

.ulist-head h3 {
    padding: 0;
}

/* height:20px из style.css мешал вертикальному центрированию значка в кнопке,
   которая теперь 28px. */
.ulist-toggle-btn {
    height: 28px;
}

.ulist-head .header-badge {
    margin-left: 0;
}

/* 12px в кружке рядом с заголовком читались мелко — цифра там короткая,
   места хватает. Высота растёт вместе с кеглем, чтобы кружок остался круглым. */
.header-badge {
    font-size: var(--fs-sm);
    font-weight: 700;
    line-height: 1;
    min-width: 22px;
    height: 22px;
    padding: 0 7px;
    border-radius: var(--r-full);
}

.header-badge.new {
    background: var(--brand-soft);
    color: var(--brand);
}

/* Кружок «онлайн» до прихода данных показывает спиннер, у которого в style.css
   размеры заданы в процентах от содержимого. С фиксированной высотой бейджа
   он превратился бы в овал — задаём явные пиксели. */
.header-badge .anim-spinner-online {
    width: 14px;
    height: 14px;
    max-height: none;
}

.all-users {
    margin: 0;
    padding: 12px;
    border-top: 1px solid var(--hairline);
    background: var(--col-bg);
}

.all-users .all-users-link {
    display: block;
    padding: 10px 12px;
    border-radius: var(--r-sm);
    background: var(--brand-soft);
    color: var(--brand);
    font-weight: 600;
    font-size: var(--fs-md);
    opacity: 1;
    box-shadow: none;
    transition: background var(--dur) var(--ease);
}

/* Заливка та же, что у активной вкладки: раньше кнопка на наведении
   выворачивалась в сплошной фиолетовый с белым текстом и била по глазам
   сильнее, чем содержимое колонки над ней. */
@media (hover: hover) {
    .all-users .all-users-link:hover {
        background: var(--brand-fill);
        color: var(--brand);
    }
}

.all-users .all-users-link:active {
    background: var(--brand-fill);
    color: var(--brand);
}

/* ---------- 5. Хаб: карточки --------------------------------------------- */

/* Карточки трёх правых колонок оформлены так же, как элементы списка
   участников (собственный фон + рамка + тень): на общем фоне колонки они
   отделяются друг от друга, а не сливаются в сплошное полотно. Раньше здесь
   были прозрачный фон и только рамка — этого не хватало.
   Наклон при наведении и подсветку граней задаёт chat_main.js, поэтому
   hover-трансформацию в CSS не дублируем: два источника дёргали бы карточку. */
.posts-list .post-item,
.comments-list .comment-item {
    margin: 0 12px 8px;
    padding: 12px;
    border-radius: var(--r-sm);
    border: 1px solid var(--card-border);
    background: var(--card-bg);
    box-shadow: var(--e1);
    transition: background .3s, transform .1s ease-out, box-shadow .2s ease-out,
                border-color var(--dur) var(--ease);
}

@media (hover: hover) {
    .posts-list .post-item:hover,
    .comments-list .comment-item:hover {
        border-color: var(--brand-soft);
    }
}

/* Внутренних разделителей у карточки нет намеренно — ни под шапкой, ни над
   подвалом. У карточки уже есть собственная рамка и фон, внутри неё две
   дополнительные линии дробят и без того маленький блок на три полосы.
   Разделение частей держится на отступах — так же, как в карточке колонки
   «Сейчас обсуждают», которая изначально была без линий.

   Правый отступ — под кнопку скрытия: она абсолютная (top:10px right:10px) и
   лежит поверх шапки карточки, а дата прижата к правому краю через
   margin-left:auto, так что без запаса они наезжали друг на друга. */
.post-header {
    display: flex;
    align-items: center;
    gap: 8px;
    padding-bottom: 0;
    padding-right: 26px;
    border-bottom: none;
}

.post-author {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin: 0;
    min-width: 0;
    font-size: var(--fs-md);
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.post-author .post-avatar,
.post-author .avatar-initial {
    width: 32px;
    height: 32px;
    margin: 0;
    font-size: var(--fs-md);
}

.post-date {
    margin-left: auto;
    flex-shrink: 0;
    font-size: var(--fs-xs);
    color: var(--muted);
}

/* В превью текст короткий и часто из нескольких строк подряд (посты с
   авторскими переносами), поэтому обычный для абзаца интервал делает карточку
   разреженной. Пустые строки при этом схлопываются ещё на стороне PHP. */
.post-item .post-content,
.comment-item .post-content {
    margin: 7px 0;
    font-size: var(--fs-md);
    line-height: 1.22;
    opacity: 1;
    color: var(--text);
}

.post-footer {
    display: flex;
    align-items: center;
    gap: 10px;
    padding-top: 0;
    border-top: none;
}

/* float:right внутри flex-строки — легаси, из-за которого счётчики
   выпадали из потока и не выравнивались по базовой линии. */
.likes-and-comments {
    float: none;
    margin: 0 0 0 auto;
    gap: 12px;
    flex-shrink: 0;
}

.post-comments-count {
    float: none;
    font-size: var(--fs-sm);
    opacity: .65;
}

.read-more {
    color: var(--muted);
    font-size: var(--fs-sm);
    font-style: normal;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* «Читать далее ›» в колонке постов — то же действие, что «Открыть ›» в
   «Сейчас обсуждают», поэтому и вид тот же. В колонке комментариев .read-more
   означает другое (превью поста, к которому оставлен комментарий), и жирным
   его набирать незачем — отсюда область действия только .posts-list. */
.posts-list .read-more {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    font-size: var(--fs-xs);
    font-weight: 600;
    transition: color var(--dur) var(--ease), transform var(--dur) var(--ease);
}

.posts-list .read-more svg {
    flex-shrink: 0;
}

@media (hover: hover) {
    .read-more:hover {
        color: var(--brand);
        opacity: 1;
    }

    .posts-list .post-item:hover .read-more {
        color: var(--brand);
    }
}

/* Кнопка скрытия контента: была невидима до наведения, то есть на тач-экране
   недоступна вовсе. Теперь всегда есть, просто приглушена. */
.hide-content-btn {
    opacity: .35;
    transition: opacity var(--dur) var(--ease), color var(--dur) var(--ease);
}

@media (hover: hover) {
    .post-item:hover .hide-content-btn,
    .comment-item:hover .hide-content-btn,
    .overall-live__item:hover .hide-content-btn {
        opacity: .8;
    }
}

.hide-content-btn:hover,
.hide-content-btn:focus-visible {
    opacity: 1;
    color: var(--alert);
}

/* Одна кнопка на два места: свёрнутая серия комментариев и свёрнутая серия
   сообщений в «Сейчас обсуждают». Ширина и поля совпадают с карточками
   колонки (у тех margin: 0 12px), поэтому кнопка встаёт ровно по их краям. */
.comments-toggle-btn {
    display: block;
    width: calc(100% - 24px);
    margin: 0 12px 8px;
    padding: 7px 14px;
    color: var(--brand);
    background: var(--brand-soft);
    border: 1px solid transparent;
    border-radius: var(--r-full);
    font-style: normal;
    font-weight: 600;
    font-size: var(--fs-xs);
    box-shadow: none;
}

/* Та же мягкая заливка, что у активной вкладки и кнопок «Все …»: сплошной
   фиолетовый с вывороткой перетягивал внимание с самих комментариев. */
@media (hover: hover) {
    .comments-toggle-btn:hover {
        background: var(--brand-fill);
        color: var(--brand);
        text-decoration: none;
    }
}

.comments-toggle-btn:active {
    background: var(--brand-fill);
    color: var(--brand);
}

/* ---------- 6. Лента активности ------------------------------------------ */

/* Ширина фиксированная, и это намеренно: лента — бегущая строка, и если бы
   полоса сжималась по содержимому, она меняла бы ширину при каждой смене
   записи. Такое мельтешение хуже пустоты.

   А вот величина была великовата. При 760px типичная запись занимала около
   трети полосы, оставляя по 200px воздуха с каждой стороны, и при
   центрированном содержимом выглядела потерянной. 620px — записи заполняют
   строку плотнее, а развёрнутому виду (лента раскрывается в список) этой
   ширины по-прежнему достаточно: там строк много и они длиннее.
   Базовое значение в style.css — 610px, так что здесь остаётся лишь небольшая
   прибавка, а не полуторакратная. */
.activity-feed-container {
    max-width: 620px;
    border: 1px solid var(--hairline);
    border-radius: var(--r-md);
    box-shadow: var(--e0);
}

/* Кнопки сворачивания появлялись по :hover — на телефоне их не существовало.
   Держим их постоянно видимыми, приглушая непрозрачностью. */
.activity-feed-close-btn,
.activity-feed-expand-btn {
    opacity: .5;
}

@media (hover: hover) {
    .activity-feed-container:hover .activity-feed-close-btn,
    .activity-feed-container:hover .activity-feed-expand-btn {
        opacity: .8;
    }
}

.activity-item {
    font-size: var(--fs-sm);
}

.activity-time {
    font-variant-numeric: tabular-nums;
    opacity: .5;
}

.activity-highlight {
    color: var(--brand);
}

/* ВОТ ОТКУДА БРАЛИСЬ «НЕРОВНЫЕ» ЛИНИИ — ключевое место, не удалять.
   У дорожки ленты в style.css задано align-items:center. Дорожка — это
   flex-контейнер с направлением column, поэтому центрирование по поперечной
   оси означает, что каждая запись сжимается ПО СВОЕМУ СОДЕРЖИМОМУ и
   центрируется. Записи разной длины — значит и ширина у них разная, а вместе
   с ней разной длины любой разделитель, хоть border-bottom, хоть
   псевдоэлемент. Правки отступов и позиционирования разделителя этого
   исправить не могли: дело было не в нём, а в ширине самой записи.
   В раскрытом виде растягиваем записи на всю ширину дорожки. Свёрнутый вид
   не трогаем — там видна одна строка, и центрирование ей на пользу. */
.activity-feed-container.expanded .activity-feed-track {
    align-items: stretch;
}

/* Раскрытая лента.
   Свёрнутый вид показывает одну строку и держится на жёсткой высоте 30px,
   overflow:hidden, nowrap и центрировании. В раскрытом виде каждое из этих
   свойств работает против нас, и обезвреживать их надо ВСЕ разом — предыдущие
   попытки правили по одному, и оставшееся тянуло вид назад: то текст срезало
   по высоте, то строки налезали друг на друга.
   Раскладка записи — строка из трёх ячеек: время фиксированной ширины,
   значок по содержимому и текст, занимающий весь остаток. За счёт последнего
   запись заполняет ширину ленты, а время и значки в соседних записях стоят
   ровно друг под другом. */
.activity-feed-container.expanded .activity-item {
    display: flex;
    /* Содержимое по центру — как в свёрнутом виде, чтобы при
       разворачивании/сворачивании записи не перепрыгивали от края к центру.
       Центрировать при этом можно только потому, что суммарная ширина группы
       у всех записей одинакова: колонка времени фиксирована, а у текста задана
       фиксированная flex-basis (см. ниже). Если вернуть тексту растяжение,
       ширина группы станет зависеть от длины строки, и время в соседних
       записях снова «запляшет» по горизонтали. */
    justify-content: center;
    gap: 8px;
    /* Отступ по бокам — чтобы первая запись не уезжала под кнопки свернуть
       и закрыть, они лежат поверх ленты в её верхних углах. Одинаковый для
       всех записей: у первой он не может отличаться, иначе колонка времени
       разъедется. */
    padding: 15px 30px;
    height: auto;
    min-height: 0;
    /* visible нужен, чтобы текст не срезался, поэтому разделитель рисуем
       псевдоэлементом, а не border-bottom (см. ниже). */
    overflow: visible;
    position: relative;
    line-height: 1.2;
    text-align: left;
    white-space: normal;
    border-bottom: none;
}

/* Разделитель отбит теми же 30px, что и содержимое. Линия во всю ширину
   контейнера при отступающем от края тексте читалась как линии разной длины:
   глаз сравнивает их не с краем блока, а с началом и концом строки, а строки
   у всех записей разной длины. Теперь линии одинаковы и совпадают по краям
   с колонкой времени слева и полем справа. */
.activity-feed-container.expanded .activity-item::after {
    content: '';
    position: absolute;
    left: 30px;
    right: 30px;
    bottom: 0;
    height: 1px;
    background: var(--hairline);
}

.activity-feed-container.expanded .activity-item:last-child::after {
    display: none;
}

.activity-feed-container.expanded .activity-time {
    flex: 0 0 auto;
    margin: 0;
    text-align: right;
}

/* Колонка времени фиксирована только там, где есть место: 7.5em хватает и на
   «Вчера 23:19:10». На узком экране она занимала бы треть строки и выдавливала
   текст в три строки, поэтому там ширина по содержимому. */
@media (min-width: 601px) {
    .activity-feed-container.expanded .activity-time {
        flex-basis: 7.5em;
    }
}

.activity-feed-container.expanded .activity-icon {
    flex: 0 0 auto;
    margin: 0;
}

/* Фиксированная flex-basis, а не растяжение по остатку. Именно она делает
   ширину группы одинаковой у всех записей и позволяет центрировать строку,
   не теряя выравнивания колонки времени. Растягиваться тексту нельзя
   (flex-grow: 0), сжиматься — можно: на узкой ленте база не поместится, и
   тогда все записи ужмутся одинаково, потому что база у них общая. */
.activity-feed-container.expanded .activity-text {
    flex: 0 1 26em;
    min-width: 0;
    overflow: visible;
    text-overflow: clip;
    white-space: normal;
}


/* Футер прятался, пока лента раскрыта (правило в style.css). Он фиксированный
   и лежит внизу экрана, а лента раскрывается внутри .cont-chat фиксированной
   высоты — перекрыть его она не может, так что и убирать его незачем:
   страница просто теряла нижнюю навигацию на ровном месте. */
body:has(.activity-feed-container.expanded) .footer {
    display: flex !important;
}

@media (hover: hover) {
    .activity-feed-container.expanded .activity-item:hover {
        background: var(--surface-hover);
    }
}

/* ---------- 7. Блок «Общий чат: последние сообщения» --------------------- */

/* Колонка появилась позже остальных, поэтому в общем правиле style.css
   (.posts-column, .comments-column, .user-column) её нет — задаём здесь. */
.overall-column {
    display: flex;
    flex-direction: column;
    /* Нужен .mouse-glare, который скрипт вкладывает внутрь колонки: в общем
       правиле style.css для остальных колонок position:relative уже есть,
       а эта появилась позже. */
    position: relative;
}

.overall-column .column-head {
    display: flex;
    align-items: center;
    padding-right: 10px;
	gap: 8px;
}

.overall-column .column-head h3 {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 1;
}

/* Компенсируем ширину кнопки обновления справа, чтобы заголовок остался по
   центру колонки. Через :has(), потому что у гостя кнопки нет и сдвигать
   заголовок там не нужно. */
.overall-column .column-head:has(.overall-live__refresh) h3 {
    padding-left: 40px;
}

/* Кнопок справа стало две (обновить + шестерёнка) — компенсация растёт вдвое,
   иначе заголовок уезжает влево от центра колонки. */
.overall-column .column-head:has(.overall-settings-btn) h3 {
    padding-left: 70px;
}

/* Шестерёнка настроек — одна и та же в колонках «Участники» и «Сейчас
   обсуждают». Правила общие намеренно: это одна кнопка с одним смыслом, и
   разные размер, наведение и активное состояние в соседних колонках читались
   как две разные кнопки. За основу взят вариант из «Сейчас обсуждают» —
   он согласован с соседней кнопкой обновления (тот же размер и та же
   приглушённость), а в колонке участников рядом стоит такая же по размеру
   кнопка справки.

   В style.css у .dialogs-settings-btn остались увеличение при наведении и
   свой цвет активного состояния — здесь они перекрыты: подложка вместо
   масштаба не двигает соседние значки в тесной шапке колонки. Поворот
   шестерёнки в активном состоянии оттуда же и сохранён — он общий для обеих. */
.dialogs-settings-btn {
    flex-shrink: 0;
    justify-content: center;
    opacity: .5;
    color: var(--text);
}

@media (hover: hover) {
    .dialogs-settings-btn:hover {
        background: var(--surface-hover);
        opacity: 1;
        transform: none;
    }
}

.dialogs-settings-btn.active {
    opacity: 1;
    color: var(--brand);
}

/* Панель настроек живёт между шапкой колонки и списком, поэтому боковые
   отступы у неё колоночные, а не как у панели «Мои диалоги» (там margin: 0 20px). */
.overall-settings-panel {
    margin: 0 10px;
}

.overall-live__refresh {
    flex-shrink: 0;
    width: 30px;
    height: 30px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: none;
    border-radius: var(--r-full);
    background: transparent;
    color: var(--text);
    opacity: .5;
    cursor: pointer;
    transition: background var(--dur) var(--ease), opacity var(--dur) var(--ease),
                color var(--dur) var(--ease);
}

@media (hover: hover) {
    .overall-live__refresh:hover {
        background: var(--surface-hover);
        opacity: 1;
        color: var(--brand);
    }
}

.overall-live__refresh:disabled {
    cursor: default;
}

/* Пока идёт запрос — значок вращается. Отдельный класс, а не :disabled:
   вращение должно останавливаться сразу после ответа, а атрибут снимается
   в том же кадре. */
.overall-live__refresh.is-busy svg {
    animation: overall-refresh-spin .7s linear infinite;
}

.overall-live__refresh.is-error {
    color: var(--alert);
    opacity: 1;
}

@keyframes overall-refresh-spin {
    to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
    .overall-live__refresh.is-busy svg {
        animation: none;
        opacity: .4;
    }
}

.live-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--ok);
    flex-shrink: 0;
    box-shadow: 0 0 0 0 rgba(47, 158, 95, .5);
    animation: live-pulse 2.4s ease-out infinite;
}

@keyframes live-pulse {
    0% { box-shadow: 0 0 0 0 rgba(47, 158, 95, .45); }
    70% { box-shadow: 0 0 0 7px rgba(47, 158, 95, 0); }
    100% { box-shadow: 0 0 0 0 rgba(47, 158, 95, 0); }
}

@media (prefers-reduced-motion: reduce) {
    .live-dot { animation: none; }
}

.overall-live {
    flex-grow: 1;
    overflow-y: auto;
    overflow-x: hidden;
    padding: 10px 0;
    margin: 0;
    list-style: none;
    /* Растушёвка у краёв прокрутки — как у .user-list, .posts-list и
       .comments-list в style.css. Значения повторены один в один: колонка
       появилась позже, в то общее правило она не попала, и её содержимое
       обрывалось резко, тогда как в трёх соседних плавно уходило. */
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 10px, #000 calc(100% - 10px), transparent 100%);
    mask-image: linear-gradient(to bottom, transparent 0, #000 10px, #000 calc(100% - 10px), transparent 100%);
}

/* Параметры наклона и подсветки повторяют .post-item / .comment-item: наклон
   при наведении и координаты «фонарика» задаёт chat_main.js по списку
   селекторов, куда карточка добавлена, а CSS отвечает за плавность и за то,
   чтобы у элемента был свой контекст наложения (position:relative нужен
   .mouse-glare, который скрипт кладёт внутрь). */
.overall-live__item {
    display: block;
    position: relative;
    /* Контейнер, а не ссылка: внутри три разных перехода (профиль, комната,
       сообщение). text-decoration и color тут больше не нужны. */
    margin: 0 12px 8px;
    padding: 10px 12px;
    border: 1px solid var(--card-border);
    border-radius: var(--r-sm);
    background: var(--card-bg);
    box-shadow: var(--e1);
    /* overflow:hidden здесь нельзя: у .mouse-glare::before задан inset:-1px,
       обводка выходит за границу карточки на пиксель и была бы срезана. */
    transition: background .3s, transform .1s ease-out, box-shadow .2s ease-out,
                border-color var(--dur) var(--ease);
    will-change: transform;
    transform-style: preserve-3d;
}

/* Собственный hover-transform здесь не задаём: его выставляет скрипт инлайном,
   и два источника дёргали бы карточку при уходе курсора. */
@media (hover: hover) {
    .overall-live__item:hover {
        border-color: var(--brand-soft);
    }
}

.overall-live__item:hover .mouse-glare {
    opacity: 1;
}

/* Колонка целиком тоже подсвечивается — как соседние. */
.overall-column:hover .mouse-glare {
    opacity: 1;
}

.overall-live__top {
    display: flex;
    align-items: center;
    gap: 4px;
    min-width: 0;
}

/* Кнопка скрытия — обычный элемент строки, а НЕ абсолютный, как в колонках
   постов и комментариев.

   Там она вынесена из потока и прибита к карточке (top:12px в style.css) —
   величина подогнана под шапку тех карточек. Здесь у карточки другой отступ
   и другая высота строки, поэтому подгонка не совпадала и крестик уезжал
   вниз; попытка центрировать коробку по строке через top:50% ту же подгонку
   лишь заменяла другой.

   В потоке подгонка не нужна вовсе: строка уже flex с align-items:center,
   она и выравнивает кнопку по времени — точно и при любых будущих правках
   отступов. Позволить себе это можно потому, что высота строки задана
   аватаром в 30px, а кнопка занимает 28–29px: в поток она встаёт бесплатно,
   не увеличивая карточку. По той же причине больше не нужен и запас справа —
   кнопка сама раздвигает строку. */
.overall-live__top .hide-content-btn {
    position: static;
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    /* line-height:1 убирает разницу между высотой глифа и строкой шрифта:
       иначе коробка центрируется, а сам знак внутри неё сидит выше центра. */
    line-height: 1;
    margin-right: -4px;
}

/* Аватар с логином — отдельная ссылка на профиль автора. Раньше вся карточка
   была одной ссылкой на сообщение, и клик по имени уводил не туда, куда
   ожидаешь. Теперь у карточки три разных назначения (профиль, комната,
   сообщение), и каждое живёт на своём элементе. */
.overall-live__who {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    text-decoration: none;
    color: inherit;
    border-radius: var(--r-xs);
}

@media (hover: hover) {
    .overall-live__who:hover .overall-live__author {
        color: var(--brand);
    }
}

.overall-live__avatar {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
}

/* Аватары в колонках мелкие — 30px в «Сейчас обсуждают» и 32px у авторов
   постов и комментариев. Общая точка в 12px на них смотрелась бы кляксой,
   поэтому здесь она меньше. Обводка цветом карточки, а не страницы: точка
   лежит на фоне карточки. */
.overall-live__top .ava-online-dot,
.post-header .ava-online-dot {
    width: 9px;
    height: 9px;
    border-width: 2px;
    border-color: var(--card-bg);
}

.overall-live__initial {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background: var(--accent);
    color: var(--avatar-initial-color, #fff);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--fs-sm);
    font-weight: 700;
    flex-shrink: 0;
}

.overall-live__author {
    font-size: var(--fs-md);
    font-weight: 600;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}

.overall-live__time {
    margin-left: auto;
    flex-shrink: 0;
    font-size: var(--fs-xs);
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}

.overall-live__text {
    margin: 6px 0 8px;
    font-size: var(--fs-md);
    line-height: 1.22;
    color: var(--text);
    /* Превью ровно в три строки: карточки одной высоты читаются как список,
       а не как рваная лесенка. */
    display: -webkit-box;
    -webkit-line-clamp: 3;
    line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    overflow-wrap: anywhere;
}

.overall-live__foot {
    display: flex;
    align-items: center;
    gap: 6px;
}

/* Чип комнаты — главный носитель смысла блока: он отвечает на вопрос
   «а где это написали». */
.overall-live__room {
    display: inline-flex;
    text-decoration: none;
    /*align-items: center;
    gap: 5px;*/
    max-width: 100%;
    padding: 4px 10px;
    border-radius: var(--r-full);
    background: var(--brand-soft);
    color: var(--brand);
    font-size: var(--fs-xs);
    font-weight: 700;
    /*line-height: 1.3;*/
    overflow: hidden;
    white-space: nowrap;
    /* Правый отступ перенесён на имя: там же живёт зона затухания, и без
       переноса чип получил бы двойной отступ справа. text-overflow убран —
       он всё равно не работал: чип это флекс-контейнер, а многоточие
       применяется только к блочному тексту, поэтому имя просто обрезалось. */
    padding-right: 0;
	align-items: center;
}

/* Длинное название комнаты не влезает в чип. Вместо резкого обреза — плавное
   затухание последних 14px.

   Почему это работает и для коротких названий тоже. Отступ справа задан не
   чипу, а самому имени, и зона затухания совпадает с ним. Если название
   короткое, текст заканчивается ДО этой зоны, и гасить там нечего — чип
   выглядит как обычно. Если длинное, текст по нехватке места протекает в
   отступ (overflow обрезает по границе внутренних полей, а не содержимого) и
   гаснет ровно на краю. Никакого JS с замером ширины не требуется. */
.overall-live__room-name {
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    padding-right: 14px;
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 14px), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 14px), transparent);
}

.overall-live__room svg {
    flex-shrink: 0;
    opacity: .8;
}

.overall-live__age {
    display: inline-flex;
    align-items: center;
    padding: 4px 8px;
    border-radius: var(--r-full);
    background: var(--alert-tint);
    color: var(--alert);
    font-size: var(--fs-2xs);
    font-weight: 700;
    flex-shrink: 0;
}

.overall-live__go {
    margin-left: auto;
    text-decoration: none;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    gap: 3px;
    font-size: var(--fs-xs);
    font-weight: 600;
    color: var(--muted);
    transition: color var(--dur) var(--ease), transform var(--dur) var(--ease);
}

@media (hover: hover) {
    /* Наведение на саму ссылку, а не на карточку: карточка целиком больше
       никуда не ведёт, и подсвечивать «Открыть» при наведении на текст
       сообщения значило бы обещать переход, которого не будет. */
    .overall-live__go:hover {
        color: var(--brand);
        transform: translateX(2px);
    }

    .overall-live__room:hover {
        background: var(--brand-fill);
    }
}

/* Скрытая часть серии сообщений одного автора. Высоту в пикселях на время
   анимации проставляет chat-hub.js — из auto переход не считается. */
.overall-run {
    max-height: 0;
    opacity: 0;
    overflow: hidden;
    transition: max-height .35s var(--ease), opacity .3s ease;
}

.overall-run.open {
    opacity: 1;
}

/* У кнопки серии в «Сейчас обсуждают» есть класс .overall-run-toggle, но
   правил под него намеренно нет: весь вид она берёт из .comments-toggle-btn,
   чтобы две одинаковые по смыслу кнопки нельзя было развести по виду правкой
   одной из них. Класс оставлен как точка привязки, если понадобится отличие. */

@media (prefers-reduced-motion: reduce) {
    .overall-run {
        transition: none;
    }
}

.overall-live__empty {
    padding: 28px 16px;
    text-align: center;
    color: var(--muted);
    font-size: var(--fs-md);
}

/* ---------- 8. Гостевой режим -------------------------------------------- */

/* Раньше гостю показывали четыре выдуманные карточки под filter:blur(4px):
   выглядело как сбой рендера, а содержимое всё равно оставалось в DOM.
   Вместо этого — честная витрина с живыми числами и одним понятным действием. */
.guest-cta {
    margin: 0 12px 14px;
    padding: 18px 16px;
    border-radius: var(--r-md);
    background: linear-gradient(150deg, var(--brand-soft), transparent 70%);
    border: 1px solid var(--hairline);
    text-align: center;
}

.guest-cta__title {
    margin: 0 0 6px;
    font-size: var(--fs-lg);
    font-weight: 700;
    color: var(--text);
}

.guest-cta__text {
    margin: 0 0 14px;
    font-size: var(--fs-sm);
    line-height: 1.5;
    color: var(--muted);
}

.guest-cta__stats {
    display: flex;
    justify-content: center;
    gap: 18px;
    margin-bottom: 14px;
}

.guest-cta__stat {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.guest-cta__num {
    font-size: var(--fs-xl);
    font-weight: 800;
    line-height: 1;
    color: var(--brand);
    font-variant-numeric: tabular-nums;
}

.guest-cta__label {
    font-size: var(--fs-2xs);
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--muted);
}

.guest-cta__btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    padding: 0 26px;
    border-radius: var(--r-full);
    background: var(--brand);
    color: var(--brand-contrast);
    font-size: var(--fs-md);
    font-weight: 700;
    text-decoration: none;
    box-shadow: var(--e1);
    transition: background var(--dur) var(--ease), transform var(--dur) var(--ease);
}

@media (hover: hover) {
    .guest-cta__btn:hover {
        background: var(--brand-strong);
        transform: translateY(-1px);
    }
}

.guest-cta__btn:active {
    transform: scale(.97);
}

/* ---------- 9. Страница диалога ------------------------------------------ */

/* Ни разделительной линии, ни собственных отступов: ниже уже идёт рамка
   .chat-box, вместе с ней черта читалась как вторая полоса, а вертикальный
   padding добавлял пустую полосу над шапкой. Отступы задаёт .cont-chat. */
.chat-header {
    gap: 6px;
}

/* Значки в шапке были собраны из разных наборов: обводка 2 / 2.1 / 2.2 / 2.5 / 3
   и размеры 26 / 28 / 32. Приводим к одной сетке — 22px и обводка 2.
   Цвет каждой кнопки НЕ трогаем: он смысловой (пауза — янтарная, звонок —
   зелёный, поиск и выход — фиолетовые), и общий var(--text) его затирал. */
.chat-header .pause-resume-btn svg,
.chat-header .history-link svg,
.chat-header .call-link svg,
.chat-header .secret-safety-btn svg {
    width: 22px;
    height: 22px;
    stroke-width: 2;
}

.chat-header .pause-resume-btn,
.chat-header .history-link,
.chat-header .call-link,
.chat-header .secret-safety-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    margin: 0;
    border-radius: var(--r-full);
    transition: background var(--dur) var(--ease), opacity var(--dur) var(--ease),
                color var(--dur) var(--ease);
}

/* При наведении кнопка «оживает» до своего полного цвета: янтарного у паузы,
   фиолетового у поиска, зелёного у звонка. В покое все они приглушены
   непрозрачностью, заданной в style.css. */
@media (hover: hover) {
    .chat-header .pause-resume-btn:hover,
    .chat-header .history-link:hover,
    .chat-header .call-link:hover,
    .chat-header .secret-safety-btn:hover {
        background: var(--surface-hover);
        opacity: 1;
        text-decoration: none;
    }

    .chat-header .dialog-search-btn:hover {
        opacity: 1;
        color: var(--brand);
    }
}

/* Кнопка отправки: значок теперь лежит в разметке (см. chat.php), поэтому
   здесь только размеры и отклик на нажатие. Ширину задаём отступами, а не
   фиксированным значением — иначе она разъедется с высотой поля ввода. */
/* Оформление кнопки отправки переехало в style.css: строка ввода одинакова
   на странице диалога и в общем чате, а chat-ui.css в общий чат не
   подключается — здесь правила достались бы только одной из двух страниц. */

/* Счётчик «0/10000» висел постоянно и занимал строку под пустым полем ввода.
   Показываем его только когда он что-то значит: поле в фокусе или в нём
   уже есть текст. Цвет счётчика по-прежнему ставит limitCounterColor()
   в chat_main.js инлайном — эту логику не трогаем. */
#message-length {
    font-size: var(--fs-xs);
    color: var(--muted);
    opacity: 0;
    transition: opacity var(--dur) var(--ease);
}

.msg-send:focus-within #message-length,
.msg-send:has(.textarea_message:not(:placeholder-shown)) #message-length {
    opacity: .8;
}

.message-input-container {
    border-radius: var(--r-md);
    box-shadow: var(--e1);
}

.chat-paused-placeholder {
    border-radius: var(--r-sm);
    font-size: var(--fs-md);
}

/* ---------- 10. Узкий экран: размеры и отступы --------------------------- */

/* Блок ниже — только оформление: отступы, кегли, размеры кнопок. Раскладка
   (сколько колонок и что закреплено) живёт в разделе 4 и переключается на
   порогах 600 и 1366px, поэтому здесь порог свой — 850px, начиная с которого
   интерфейс уже стоит поджать, но колонок ещё две.

   Про нижнюю навигацию с position:fixed здесь пришлось отказаться: у .st-pusher
   постоянно задан transform:translate3d(0,0,0), а это делает его содержащим
   блоком для fixed-элементов. Сам .st-pusher высотой ровно 100dvh и при
   прокрутке страницы остаётся на месте, поэтому bottom:0 внутри него означает
   не «низ экрана», а «низ первого экрана» — панель уезжала бы вверх при
   скролле. Ровно об эту особенность спотыкались панель диалогов и модалка
   медиа, их поэтому и вынесли за пределы контейнера.
   Решение без переноса разметки: ряд разделов остаётся обычной строкой в
   потоке, а закрепляется то, что действительно нужно под рукой во время
   чтения длинного списка, — переключатель вкладок (см. раздел 4). Единственным
   входом в диалоги на узком экране остаётся плавающая кнопка. */
@media (max-width: 850px) {
    /* Перенос на вторую строку убран: как только к трём разделам добавлялась
       кнопка возврата ленты, ряд переставал помещаться в 360px и разваливался.
       Вместо переноса — горизонтальная прокрутка: width:fit-content с
       автополями центрирует ряд, пока он влезает, и позволяет пролистать его,
       когда нет. Так он не сломается ни при более длинном названии раздела,
       ни при крупном системном шрифте. */
    .chat-btns {
        position: static;
        flex-wrap: nowrap;
        justify-content: flex-start;
        width: fit-content;
        max-width: 100%;
        margin-inline: auto;
        overflow-x: auto;
        overscroll-behavior-x: contain;
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
        gap: 5px;
        /* Запас сверху обязателен: бейдж непрочитанного висит на -7px выше
           кнопки, а прокрутка по горизонтали делает контейнер обрезающим и по
           вертикали тоже — без отступа бейдж срезало бы. */
        padding: 10px 2px 12px;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        background: none;
    }

    .chat-btns::-webkit-scrollbar {
        display: none;
    }

    .dark-theme .chat-btns {
        background: none;
    }

    /* Размеры подобраны так, чтобы весь ряд вместе с кнопкой возврата ленты
       помещался в 320px без прокрутки: при прежних значениях «Посты»
       оказывались за краем, как только лента свёрнута. Прокрутка ряда
       остаётся как страховка на случай более длинных подписей. */
    .chat-btns .all-users-link.overallchat-btn {
        flex-shrink: 0;
        gap: 5px;
        padding: 0 10px;
        min-height: 32px;
        font-size: var(--fs-xs);
    }

    .chat-btns .all-users-link.overallchat-btn .btn-ico {
        width: 16px;
        height: 16px;
    }

    /* Кнопка возврата ленты — по высоте вровень с соседями. Ширину меняем
       и в развёрнутом состоянии тоже, иначе анимация появления вернула бы 36px. */
    .chat-btns .activity-feed-toggle-btn,
    .chat-btns .activity-feed-toggle-btn.feed-btn-visible {
        height: 32px;
        width: 30px;
        flex-shrink: 0;
    }

    /* Зазор ряда здесь 5px, рамки те же 2px — компенсация меньше, чем на
       десктопе (см. комментарий у базового правила). */
    .chat-btns .activity-feed-toggle-btn {
        margin-right: -7px;
    }

    .chat-btns .activity-feed-toggle-btn.feed-btn-visible,
    .chat-btns .activity-feed-toggle-btn.feed-btn-pop {
        max-width: 30px;
        margin-right: 0;
    }

    /* Плавающая кнопка — единственная точка входа в диалоги на мобильном,
       поэтому она заметнее, но без прежнего чужеродного градиента. */
    .dialogs-fab-btn {
        box-shadow: var(--e2);
    }

    /* Отступы кнопки отправки заданы в пикселях (в em они схлопывались вместе
       с размером шрифта), поэтому мобильное правило из style.css их больше не
       перебивает — уменьшаем здесь явно. */
    .cont-chat {
        padding-left: 10px;
        padding-right: 10px;
    }

    .column {
        --col-r: var(--r-md);
    }

    .posts-list .post-item,
    .comments-list .comment-item,
    .overall-live__item {
        margin-left: 8px;
        margin-right: 8px;
    }
}


/* =========================================================================
   Лента сообщений в диалоге — ОТКАЧЕНО

   Здесь был блок правил, переводивший ленту с float на flex, склеивавший
   серии сообщений, добавлявший разделители дней и собиравший кнопки действий
   в одну панель. Всё это откачено по итогам проверки на реальной переписке:

     — реакции под сообщением наезжали на время: их блок не был учтён,
       когда я убрал резерв высоты под кнопки;
     — разделители дней были сделаны sticky и при прокрутке наползали друг
       на друга;
     — порог склейки в 5 минут не совпадает с тем, как люди реально пишут:
       сообщения с разрывом в 5–20 минут воспринимаются как одна серия,
       а по этому правилу расклеивались;
     — сгруппированные пузыри без имени и с разной шириной выглядели рвано;
     — панель действий на телефоне получилась крупной, перекрывала соседние
       сообщения, и по ней легко было промахнуться.

   Вывод на будущее: это единственное место в проекте, где нельзя проверять
   вёрстку на выдуманных данных. Нужны настоящая переписка, реакции, вложения
   и длинная история — на синтетическом стенде все пять дефектов невидимы.
   Возвращаться к этому стоит по одному изменению за раз и только с проверкой
   на живом диалоге.
   ========================================================================= */

/* ---------- Цвет значков у кнопок сообщения -------------------------------
   Значки переведены с зашитого stroke="#777" на currentColor, чтобы следовать
   теме. Но цвет был задан не всем кнопкам: у .copy-btn, .quote-btn и
   .edit-btn стоит color:#666, а у .delete-btn, .reaction-btn и .save-msg-btn
   его не было вовсе — раньше это не имело значения, потому что цвет линии был
   вписан прямо в разметку. После перехода они унаследовали цвет текста пузыря
   и стали чёрными. Задаём общий приглушённый тон — он же светлеет в тёмной
   теме, чего прежний #777 не делал. */
.copy-btn,
.quote-btn,
.edit-btn,
.delete-btn,
.reaction-btn,
.restore-btn,
.save-msg-btn {
    color: var(--muted);
}

/* Правило из style.css жёстко красило значок копирования в тёмной теме и
   перебивало currentColor важностью. Возвращаем наследование. */
.dark-theme .copy-btn svg {
    stroke: currentColor !important;
}

/* ---------- Кнопка «сохранить» -------------------------------------------
   Раньше это была эмодзи ⭐: цветной глиф, к которому и применялись
   filter:grayscale(0) и жёлтая тень — так он «загорался» в отмеченном
   состоянии. Со значком-контуром оба приёма бессмысленны: серости в нём нет,
   а тень по чёрной линии выглядит грязно. Отмеченное состояние показываем
   цветом самой линии.

   Прыжок при наведении — от scale(1.2) поверх translateY(-50%), который
   держит кнопку по центру: масштаб пересобирал трансформацию целиком, и
   кнопка дёргалась. Сдвиг вбок при наведении на пузырь оставляем, он был
   и раньше, — убираем только масштаб. */
.save-msg-btn:hover {
    transform: translateY(-50%) !important;
    filter: none !important;
    color: #d99b1f;
}

.save-msg-btn.saved,
.message-bubble.show-actions .save-msg-btn.saved {
    filter: none !important;
    color: #d99b1f;
}

.dark-theme .save-msg-btn:hover,
.dark-theme .save-msg-btn.saved {
    color: #e8b74a;
}

/* ---------- Разделители дат в диалоге -------------------------------------
   Разметку собирает chat_main.js (initDateSeparators). Здесь важен один
   принцип: разделитель в потоке НЕ делается sticky. Именно sticky-разделители
   наползали друг на друга в прошлой попытке (см. откаченный блок выше) —
   несколько закреплённых элементов с одинаковым top собираются в стопку у
   верхней кромки. Роль «прилипшей» подписи целиком отдана .chat-date-float:
   она одна на ленту и лежит вне прокрутки, поэтому накладываться не на что. */

.chat-date-sep {
    display: flex;
    justify-content: center;
    pointer-events: none;
    /* Пузыри в ленте — плавающие (.my-message/.other-message: float + clear,
       см. style.css). Без clear разделитель встал бы сбоку от последнего
       сообщения, а не отдельной строкой. */
    clear: both;
    width: 100%;

    /* Отступ сверху задан через padding по исторической причине: пока лента
       стояла на float, верхний margin у очищающего блока заменялся клиренсом
       и зазор пропадал — разделитель прилипал к предыдущему сообщению.
       Лента переведена на flex, клиренса больше нет, и margin здесь работал
       бы наравне. Оставлено как есть намеренно: величины выверены на живой
       переписке, а замена padding на margin — правка без выигрыша, но с
       возможностью промахнуться. */
    padding-top: 16px;
    margin: 0 0 6px;

    /* Снизу зазор складывается из двух величин, и это надо держать в уме при
       правках: собственный margin-bottom (6px) ПЛЮС margin-top:10px у
       следующего пузыря. Схлопывания между ними нет — между flex-элементами
       margin не схлопывается (как не схлопывался и между float). Итого 16px
       сверху и 16px снизу: подпись ровно посередине разрыва между днями. */

    /* Из якорей прокрутки исключён намеренно: разделители появляются и
       исчезают при пересборке, и браузер, выбрав такой якорь, дёргал бы
       ленту при догрузке истории. */
    overflow-anchor: none;
    animation: dateSepAppear .3s var(--ease) both;
}

@keyframes dateSepAppear {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: none; }
}

.chat-date-sep span,
.chat-date-float {
    padding: 3px 12px;
    border-radius: var(--r-full);
    font-size: var(--fs-2xs);
    font-weight: 600;
    letter-spacing: .02em;
    white-space: nowrap;
    color: var(--muted);
    background: var(--msg-bg);
    border: 1px solid var(--hairline);
    box-shadow: var(--e0);
}

/* Плавающая подпись дня. position:absolute от .chat-box — не от самой ленты:
   лента прокручивается, и плашка уезжала бы вместе с сообщениями. */
/* left задаётся из chat_main.js (alignDateFloat) по центру содержимого ленты —
   без полосы прокрутки, иначе плашка не совпадает с пилюлями в потоке.
   Значение здесь — запасное, до первого пересчёта. */
.chat-date-float {
    position: absolute;
    top: 10px;
    left: 50%;
    z-index: 5;
    transform: translate(-50%, -8px);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    box-shadow: var(--e1);
    transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease), visibility var(--dur);
}

.chat-date-float.is-visible {
    opacity: 1;
    visibility: visible;
    transform: translate(-50%, 0);
}

/* На телефоне пузыри стоят плотнее (margin-top:6px в style.css) — сжимаем
   и отступ разделителя, иначе он выпадает из ритма ленты. */
@media (max-width: 768px) {
    /* Пузырь снизу отступает на 6px, поэтому и margin-bottom здесь меньше —
       чтобы сумма (4+6) осталась равна отступу сверху. */
    .chat-date-sep {
        padding-top: 10px;
        margin-bottom: 4px;
    }

    .chat-date-float {
        top: 6px;
    }
}

@media (prefers-reduced-motion: reduce) {
    .chat-date-float {
        transition: opacity .01s linear, visibility .01s linear;
        transform: translate(-50%, 0);
    }

    .chat-date-sep {
        animation: none;
    }
}

/* =========================================================================
   Строение сообщения

   Раньше В ПУЗЫРЕ АБСОЛЮТОМ СТОЯЛО ВСЁ, кроме текста: шесть кнопок с зашитыми
   координатами (left:5, left:45, right:5, right:42, right:80 — в трёх разных
   файлах), блок реакций по центру нижней строки и время с ручным резервом
   margin-top:14px под всё это. Пока ширина пузыря была жёстко 80/85%, места
   хватало любому сочетанию, и никто этого не замечал. Как только пузырь стал
   сжиматься по содержимому, каждая координата превратилась в ограничение, и
   ширину, при которой ничего не накладывается, пришлось выводить формулой
   в JS — с замером времени, шириной реакций и потолком по дорожке.

   Теперь у пузыря две части вместо россыпи:
     .msg-footer   — реакции и время, ОБЫЧНЫМ ПОТОКОМ. Ширину под них считает
                     браузер, поэтому формула из JS удалена целиком;
     .msg-actions  — одна плашка кнопок, единственный абсолютный блок. Она вне
                     потока, поэтому её появление не двигает ничего вокруг.

   Резервов под кнопки больше нет нигде: ни margin-right у логина, ни распорки
   в тексте сгруппированного сообщения, ни min-width у пузыря.
   ========================================================================= */

/* ---------- Подпись автора ------------------------------------------------

   В диалоге подписи нет: собеседник один и назван в шапке вместе с аватаром,
   а повтор логина над каждой репликой — шум, которого нет ни в одном
   современном мессенджере.

   Элемент при этом ОСТАЁТСЯ в разметке, только скрыт. Его читают три места:
   ключ серии сообщений (applyMessageGrouping), автор при открытии правки и
   перерисовка пузыря после сохранения. Удалить его из разметки — сломать всё
   три, поэтому убираем именно из вида.

   Исключение — пересланное сообщение. В «Избранном» лежат реплики разных
   людей, и подпись там единственное, что говорит, чьи они. Метку is-forwarded
   ставит chat_main.js при сборке пузыря. */
.message-bubble > .user-name {
    display: none;
}

.message-bubble.is-forwarded > .user-name {
    display: inline-block;
}

/* Подписи больше нет — её верхний отступ надо вернуть всем пузырям, а не
   только сгруппированным: у пузыря padding-top всего 1px, и без этого текст
   упирался бы в самый край. */
.message-bubble {
    padding-top: 7px;
}

/* ---------- Серии сообщений ---------------------------------------------- */

/* Класс ставит chat_main.js (applyMessageGrouping) второму и последующим
   сообщениям подряд от одного автора в пределах дня. Скрытием подписи он
   больше не заведует — она скрыта у всех; осталась только плотность серии.
   Если лента покажется рваной, эта строка и есть то, что нужно убрать
   первым. */
.message-bubble.is-grouped {
    margin-top: 5px;
}

/* ---------- Ширина по содержимому ----------------------------------------

   width:80%/85% в style.css заданы с !important, поэтому и здесь !important.
   Прежние проценты переходят в max-width — это и был верхний предел.

   Никакого min-width больше нет, и это главный итог перестройки. Он был нужен,
   пока время, реакции и кнопки стояли в пузыре абсолютом: ширину, при которой
   они не наезжают друг на друга, приходилось вычислять в JS. Теперь время и
   реакции лежат в строке-подвале обычным потоком, поэтому пузырь не может
   стать уже собственного содержимого — это гарантия раскладки, а не расчёта.
   ------------------------------------------------------------------------- */

.message-bubble.my-message,
.message-bubble.other-message {
    width: fit-content !important;
}

.message-bubble.my-message {
    max-width: 80%;
}

.message-bubble.other-message {
    max-width: 85%;
}

/* ---------- Строка-подвал: реакции и время -------------------------------- */

.msg-footer {
    display: flex;
    align-items: center;
    /* Реакции прижаты влево, время вправо. Между ними — резиновый зазор, но
       не меньше 10px, иначе при узком сообщении они сходятся вплотную. */
    justify-content: space-between;
    gap: 10px;
    margin-top: 2px;
}

/* Высоту кружка реакции резервируем только там, где реакции есть: иначе
   каждое обычное сообщение стало бы выше на пустом месте.

   Оба размера — и минимум подвала, и высота самой строки реакций — переходят
   плавно. Без этого постановка реакции дёргала сообщение: подвал мгновенно
   вырастал с 18 до 26px, и вся лента ниже прыгала на восемь пикселей.
   Базовые 18px заданы явно (это высота строки времени), иначе переход шёл бы
   от auto и не анимировался вовсе. */
.msg-footer {
    min-height: 18px;
    transition: min-height var(--dur) var(--ease);
}

.message-bubble.has-reactions .msg-footer {
    min-height: 26px;
}

/* Строка реакций тоже растёт из нуля, а не появляется готовой высотой:
   26px — фиксированный размер кружка, поэтому переход считается (в отличие
   от height:auto, который не анимируется). Класс has-reactions ставится
   кадром позже вставки кружков — см. reactions.js, иначе у перехода не было
   бы начального состояния. */
.msg-footer .message-reactions {
    height: 0;
    transition: height var(--dur) var(--ease);
}

.message-bubble.has-reactions .msg-footer .message-reactions {
    height: 26px;
}

/* Без реакций подвал состоит из одного времени — прижимаем его вправо. */
.message-bubble:not(.has-reactions) .msg-footer {
    justify-content: flex-end;
}

.msg-footer .message-reactions {
    /* Было absolute по центру нижней строки; в потоке позиционирование
       ни к чему, и именно оно требовало расчёта ширины пузыря. */
    position: static;
    transform: none;
    gap: 6px;
    z-index: auto;
}

.message-reactions:empty {
    display: none;
}

/* Время больше не резервирует место под кнопки: они уехали из пузыря.
   margin-top:14px в style.css был именно этим резервом — снимаем, сообщения
   становятся ниже на те же 14px каждое. */
.message-bubble .message-time {
    margin-top: 0;
    display: inline-flex;
    align-items: center;
    padding-left: 0;
    white-space: nowrap;
    /* Высота строки задана явно и равна самому высокому, что в ней бывает, —
       кнопке отмены отправки (18px). Иначе высота подвала зависела бы от того,
       есть кнопка в строке или нет: у отправляемого сообщения она появляется
       на пятой секунде, а при подтверждении исчезает, и подвал то вырастал,
       то садился, подбрасывая сообщение. Теперь строка одной высоты во всех
       состояниях: и при отправке, и при доставке, и при ошибке. */
    line-height: 18px;
}

/* Подвал удалённого сообщения состоит из одного времени — прижимаем вправо,
   как и у обычных сообщений без реакций (кнопка восстановления уехала
   в общую плашку, см. placeRestoreButton). */
.message-bubble.is-deleted .msg-footer {
    justify-content: flex-end;
}

/* ---------- Плашка действий ------------------------------------------------

   Единственный абсолютный блок пузыря. Он ВНЕ потока, поэтому появление
   кнопок не меняет раскладку — ни рывка, ни сдвига соседей, ни изменения
   ширины пузыря. Плашка висит над сообщением и слегка перекрывает зазор до
   предыдущего; собственные фон, рамка и тень делают её отдельным слоем, а не
   наложением (так же ведут себя действия над сообщением в Discord и Telegram).

   Сторона привязки разная не для красоты: плашка шире узкого пузыря, и при
   единой привязке она вылезала бы за дорожку сообщений, где её обрезает
   overflow ленты. Своё сообщение прижато вправо — плашку растим влево;
   чужое прижато влево — вправо.
   ------------------------------------------------------------------------- */

.msg-actions {
    position: absolute;
    bottom: 100%;
    margin-bottom: -10px;
    display: flex;
    align-items: center;
    gap: 2px;
    padding: 3px;
    border-radius: var(--r-full);
    background: var(--msg-bg);
    border: 1px solid var(--hairline);
    box-shadow: var(--e2);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transform: translateY(4px);
    transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease), visibility var(--dur);
    z-index: 6;
}

.message-bubble.my-message .msg-actions {
    right: 8px;
}

.message-bubble.other-message .msg-actions {
    left: 8px;
}

@media (hover: hover) {
    .message-bubble:hover .msg-actions {
        opacity: 1;
        visibility: visible;
        pointer-events: auto;
        transform: translateY(0);
    }
}

.message-bubble.show-actions .msg-actions,
.message-bubble:focus-within .msg-actions {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: translateY(0);
}

/* Кнопки внутри плашки: снимаем абсолютное позиционирование и прежнюю
   механику показа — видимостью теперь управляет сама плашка. Перечислены
   поимённо, потому что координаты у каждой были свои (left:5, left:45,
   right:5, right:42, right:80) и заданы в трёх разных файлах. */
.msg-actions .copy-btn,
.msg-actions .quote-btn,
.msg-actions .edit-btn,
.msg-actions .delete-btn,
.msg-actions .reaction-btn,
.msg-actions .save-msg-btn,
.msg-actions .restore-btn {
    position: static;
    top: auto;
    right: auto;
    bottom: auto;
    left: auto;
    transform: none;
    opacity: .8;
    /* visibility:inherit, а НЕ visible: иначе кнопка осталась бы видимой
       внутри скрытой плашки (visibility скрывает потомков, но потомок вправе
       объявить себя видимым). pointer-events снимаем по той же причине —
       невидимая плашка висит над зазором к предыдущему сообщению, и кликабельные
       кнопки в ней перехватывали бы нажатия вслепую. Обратно включаются ниже,
       ровно в тех состояниях, когда плашка показана. */
    visibility: inherit;
    pointer-events: none;
    z-index: auto;
    filter: none;
    width: 28px;
    height: 28px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--r-full);
    transition: background var(--dur) var(--ease), opacity var(--dur) var(--ease), color var(--dur) var(--ease);
}

/* Нажатия включаются только вместе с показом плашки — состояния те же. */
.message-bubble:hover .msg-actions button,
.message-bubble.show-actions .msg-actions button,
.message-bubble:focus-within .msg-actions button {
    pointer-events: auto;
}

/* Звёздочка «сохранить» раньше выезжала за пределы пузыря, и её сдвиг задан
   в style.css через transform с !important. В ряду кнопок этот сдвиг поднимал
   бы её над соседями. */
.msg-actions .save-msg-btn,
.msg-actions .save-msg-btn:hover {
    transform: none !important;
}

@media (hover: hover) {
    .msg-actions button:hover {
        opacity: 1;
        background: var(--surface-hover);
        /* Прежние правила увеличивали кнопку при наведении. В ряду вплотную
           стоящих кнопок это цепляет соседей — вместо масштаба подсвечиваем
           подложку. */
        transform: none !important;
    }
}

.msg-actions .delete-btn:hover {
    color: var(--alert);
}

/* «Редактировать» и «Цитировать» задавали цвет наведения ещё до появления
   плашки, в style.css: карандаш темнел до #333, кавычки красились в #6c5ce7.
   Оба значения рассчитаны на светлый фон — на подложке плашки в тёмной теме
   (--msg-bg #333) карандаш сливался с ней полностью, а фиолетовый уходил
   в темноту. Берём цвет темы: он светлый в тёмной теме и тёмный в светлой,
   и совпадает с остальной подсветкой действий.

   Селектор на класс длиннее исходного (три уровня против двух) — иначе
   правило из style.css не перебить; !important у прозрачности — потому что
   у .quote-btn:hover она объявлена с ним же. */
.msg-actions .edit-btn:hover,
.msg-actions .quote-btn:hover {
    color: var(--brand);
    opacity: 1 !important;
}

.msg-actions .save-msg-btn.saved {
    opacity: 1;
}

/* Звёздочке в style.css заданы отдельные прозрачности для :hover пузыря (.4)
   и для .show-actions (.6) — они были рассчитаны на кнопку, висящую снаружи.
   В ряду с остальными она из-за этого выглядела бы бледнее соседей; селектор
   тут не короче исходных, иначе не перебить. */
.message-bubble .msg-actions .save-msg-btn:not(.saved) {
    opacity: .8;
}

/* Плашка не должна перехватывать наведение у соседнего сообщения: она висит
   над пузырём, и без этого правила курсор в зазоре между сообщениями оставлял
   бы её открытой. Реагирует только сама плашка, когда уже видима. */
.message-bubble:not(:hover):not(.show-actions):not(:focus-within) .msg-actions {
    pointer-events: none;
}

/* ---------- Содержимое с процентной шириной -------------------------------
   Ловушка сжимающегося по содержимому контейнера: у ребёнка с width:100%
   при расчёте собственной ширины родителя процент считается как auto. Для
   встраиваний это ноль — видео и карточки схлопнулись бы в полоску. Даём
   каждому явную основу в пикселях, а прежний предел оставляем как max-width
   от пузыря. */
.message-bubble .ve-16x9 {
    width: 500px;
    max-width: 100%;
}

.message-bubble .ve-tiktok {
    width: 325px;
    max-width: 100%;
}

.message-bubble .ve-og,
.message-bubble .ve-fallback {
    width: 460px;
    max-width: 100%;
}

.message-bubble .video-container {
    width: 420px;
    max-width: 100%;
}

/* У голосовых ширина зашита в inline-стиль самого <audio> (width:100% в
   chat_main.js), перебить её нельзя — задаём размер обёртке, процент
   считается от неё. */
.message-bubble .voice-audio-wrap,
.message-bubble .audio-container,
.message-bubble .audio-message {
    width: 320px;
    max-width: 100%;
}

.message-bubble .deleted-image-stub {
    width: 200px;
}

/* Правка сообщения разворачивается прямо в пузыре, у поля ввода width:100%.
   Без собственного минимума пузырь сжался бы до ширины пустой textarea. */
.message-bubble .edit-textarea {
    min-width: min(420px, 60vw);
}

/* =========================================================================
   Доступность действий над сообщением

   Кнопки в пузыре скрыты через visibility:hidden и pointer-events:none и
   показываются только по :hover и .show-actions (style.css). Элемент с
   visibility:hidden не получает фокус ВООБЩЕ — то есть скопировать,
   процитировать, отредактировать или удалить сообщение с клавиатуры было
   нельзя, и глобальное кольцо фокуса из style.css до этих кнопок не доходило
   просто потому, что дойти было некуда.

   :focus-within — тот же переключатель, что и :hover, но для клавиатуры:
   как только фокус попадает внутрь пузыря, ряд кнопок проявляется, и по нему
   можно идти табом.

   Поштучного управления кнопками здесь больше нет: показом ведает плашка
   .msg-actions (см. раздел о строении сообщения), и :focus-within — один из
   трёх её переключателей наравне с :hover и .show-actions. Осталась только
   подсветка пузыря, чтобы с клавиатуры было видно, к какому сообщению
   относится проявившийся ряд.
   ========================================================================= */

.message-bubble:focus-within {
    background-color: var(--bubble-active);
}

/* =========================================================================
   Служебные строки ленты: единый вид

   В ленте три вида строк, которые не являются репликами: разделитель даты,
   отсечка «Новые сообщения» и событие звонка. Раньше каждая была оформлена
   сама по себе — свои кегли, свои зашитые серые (#888, #d1d1d1, #555),
   свои радиусы. Сводим их к одной форме: приглушённая пилюля по центру
   ленты. Разделитель даты уже такой, здесь к нему подтягиваются остальные.
   ========================================================================= */

/* ---------- Событие звонка ------------------------------------------------ */

/* display был inline-block, при этом рядом стояли align-items, justify-content
   и gap — все три в inline-block не работают, поэтому значок с текстом
   разделял обычный пробел. Делаем строку настоящим flex-рядом. */
.call-service-msg {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 5px 12px;
    border-radius: var(--r-full);
    background: var(--callmsg-bg);
    border: 1px solid var(--hairline);
    font-size: var(--fs-xs);
    color: var(--callmsg-color);
}

.call-icon {
    display: inline-flex;
    /* Значок оптически тяжелее текста — гасим, чтобы строка читалась
       как единое целое, а не как «иконка и подпись». */
    opacity: .85;
}

/* Цвет значка несёт статус: пропущенный выделен, остальные нейтральны.
   Красим только линию значка, не весь текст, — иначе служебная строка
   начинает спорить с сообщениями за внимание. */
.call-service-msg.is-missed .call-icon {
    color: var(--alert);
    opacity: 1;
}

.call-service-msg.is-completed .call-icon {
    color: var(--ok);
}

.call-time {
    font-size: var(--fs-2xs);
    color: var(--calltime-color);
    margin-left: 2px;
}

/* ---------- Отсечка «Новые сообщения» ------------------------------------- */

/* Было: color:#888 и линии #d1d1d1 в обеих темах (в тёмной линии сливались
   с фоном, а на мобильном стоял ещё и #555). Переводим на токены и на ту же
   форму пилюли, что у даты, но в акцентном тоне — это единственная из трёх
   строк, на которую пользователь должен обратить внимание. */
.unread-messages-divider {
    color: var(--brand);
    font-size: var(--fs-2xs);
    font-weight: 600;
    gap: 12px;
}

.unread-messages-divider span {
    padding: 3px 12px;
    border-radius: var(--r-full);
    background: var(--brand-soft);
    border: 1px solid var(--brand-fill);
    white-space: nowrap;
    letter-spacing: .02em;
}

.unread-messages-divider::before,
.unread-messages-divider::after {
    background-color: var(--brand-fill);
    /* Отступы задаёт gap на самой строке — поля у псевдоэлементов больше
       не нужны и мешали бы симметрии. */
    margin: 0;
}

/* ---------- Метаданные сообщения ------------------------------------------ */

/* Время было #828282 поверх opacity:.7 — по факту около #a8a8a8 на белом,
   контраст примерно 2.4:1 при норме 4.5:1 для мелкого текста. Прозрачность
   убираем совсем: она гасила заодно и кружок статуса, и кнопки отмены с
   повтором внутри строки. Токен --muted посчитан под 4.5:1 в обеих темах. */
.message-bubble .message-time {
    color: var(--muted);
    opacity: 1;
}

/* Логин — та же история: #666 с opacity:.7. Правило осталось нужным несмотря
   на то, что подпись в диалоге скрыта: у пересланных сообщений она видна. */
.message-bubble .user-name {
    color: var(--muted);
    opacity: 1;
}

/* ---------- Подсказка о прокрутке ----------------------------------------
   will-change:scroll-position на ленте (style.css) — устаревшая подсказка:
   современные движки и так знают, что скроллер будет скроллиться, а объявление
   заставляет держать для него отдельный слой всё время, пока диалог открыт.
   В WebView приложения это не бесплатно: лишний слой занимает память и
   участвует в композитинге на каждом кадре. Снимаем. */
.messages-container {
    will-change: auto;
}

/* ---------- Плашка действий на телефоне -----------------------------------
   Здесь прошлая попытка и провалилась: панель получилась крупной, ложилась
   на соседние сообщения и по ней легко было промахнуться. Разница теперь в
   том, что плашка привязана к своему пузырю и открывается по касанию
   (.show-actions), а не висит постоянно. Кнопки увеличиваем до 32px — это
   выше минимума 24×24 из WCAG 2.5.8 и попадает пальцем. */
@media (max-width: 768px) {
    .msg-actions {
        gap: 4px;
        padding: 4px;
    }

    .msg-actions button {
        width: 32px;
        height: 32px;
    }
}

/* ---------- Кнопка отмены отправки: ширина без рывка -----------------------
   В style.css у неё display:inline-flex с visibility:hidden и показом через
   анимацию с задержкой в пять секунд. Скрытая кнопка при этом ЗАНИМАЕТ МЕСТО:
   18px плюс поля. Пока пузырь был шириной ровно 80%, разницы не было видно;
   с шириной по содержимому получилось так, что «отправляемое» сообщение на
   22px шире себя же подтверждённого — и в момент смены кружка-загрузки на
   статус прочтения пузырь дёргался.

   Схлопываем скрытую кнопку в нулевую ширину и возвращаем её в тех же
   ключевых кадрах, что уже управляют показом. Ширина и поля анимируемы,
   поэтому появление кнопки на пятой секунде теперь ещё и плавное, а не
   скачком. Задержку и логику не трогаем — они в style.css. */
.my-message.pending-message .msg-cancel-btn {
    /* min-width:0 обязателен, и вот почему. Строка времени — флекс-контейнер
       (.message-time, display:inline-flex), значит кнопка внутри неё это
       флекс-элемент, а у него действует min-width:auto: он не сжимается
       меньше собственного содержимого. Одного width:0 не хватало — кнопка
       продолжала занимать ширину своей иконки в 12px, и при подтверждении
       сообщения пузырь на эти пиксели схлопывался.

       height:0 по той же причине нужен отдельно: кнопка 18px высотой была
       выше строки времени (~15px) и задавала высоту всему подвалу. При смене
       состояния подвал становился ниже — сообщение подбрасывало вверх. */
    width: 0;
    min-width: 0;
    height: 0;
    min-height: 0;
    margin: 0;
    overflow: hidden;
    animation: msgCancelReveal .18s var(--ease) 5s forwards;
}

/* Возвращаем ровно то, что забрали выше. Ширина и высота анимируемы, поэтому
   появление кнопки на пятой секунде — плавное раскрытие, а не скачок. */
@keyframes msgCancelReveal {
    to {
        visibility: visible;
        width: 18px;
        height: 18px;
        margin: 0 2px;
    }
}

/* Кнопка повтора появляется только у сообщения с ошибкой — там изменение
   ширины уместно, это смена состояния, которую нужно заметить. */
.my-message.failed-message .msg-retry-btn {
    width: 18px;
    margin: 0 2px;
}

@media (prefers-reduced-motion: reduce) {
    .my-message.pending-message .msg-cancel-btn {
        animation-duration: .01s;
        animation-delay: 5s;
    }
}

/* =========================================================================
   Лента: с float на flex

   Пузыри стояли на float:right/left с clear:both — приёме из времён, когда
   другого способа прижать блок к краю не было. Он дважды укусил нас в этой
   же сессии:

     — у элемента, очищающего float, верхний margin ЗАМЕНЯЕТСЯ клиренсом,
       поэтому разделитель даты прилипал к предыдущему сообщению, и отступ
       пришлось задавать через padding;
     — float требует, чтобы у ленты был способ «закрыть» обтекание, и любой
       новый блок в ней (разделитель, отсечка, шапка поиска) обязан помнить
       про clear, иначе встаёт сбоку от сообщения.

   Flex-колонка убирает оба явления как класс: край задаётся align-self,
   клиренса не существует, margin работает как написано. Полноширинные блоки
   ленты — заглушка, кнопка догрузки, «Нет сообщений», разделители, шапка
   результатов поиска, плашки секретного чата — растягиваются сами (align-items
   по умолчанию stretch), поэтому в разметке для них ничего не меняется.

   Отступы НАМЕРЕННО оставлены на margin у самих сообщений, а не переведены
   на gap ленты: у серий сообщений свой отступ (.is-grouped), у разделителей
   свой, и gap стёр бы эту разницу. Значения при этом совпадают со старыми —
   между flex-элементами margin не схлопывается ровно так же, как не
   схлопывался между float.
   ========================================================================= */

.messages-container {
    display: flex;
    flex-direction: column;
}

/* ОБЯЗАТЕЛЬНОЕ правило, а не оптимизация. В колоночном flex сжатие идёт по
   вертикали, и когда содержимое длиннее ленты, браузер сначала пытается
   ужать элементы, а уже потом отдаёт остаток в прокрутку. Обычно спасает
   min-height:auto — элемент не ужимается меньше своего содержимого, — но это
   правило отключается у всего, чей overflow отличается от visible.

   Именно так сплющило отсечку «Новые сообщения»: у неё в style.css стоит
   overflow:hidden (он нужен её затуханию через max-height), из-за него
   минимальная высота стала нулевой, и flex выжал её в полоску, пока соседние
   пузыри с overflow:visible сжиматься отказывались.

   Запрещаем сжатие всему, что лежит в ленте: любой будущий блок с overflow
   или max-height получил бы ту же болезнь, причём выглядела бы она как
   «вёрстка поехала», а не как ошибка раскладки. */
.messages-container > * {
    flex-shrink: 0;
}

.message-bubble.my-message {
    float: none;
    align-self: flex-end;
}

.message-bubble.other-message {
    float: none;
    align-self: flex-start;
}

/* Служебные сообщения о звонках центрируются своим margin:0 auto из
   style.css — в flex автоматические поля забирают свободное место так же,
   как в блочном потоке. align-self задан явно, чтобы намерение читалось. */
.message-bubble.service-message-bubble {
    float: none;
    align-self: center;
}

/* clear:both у пузырей и у разделителя даты в flex-контексте не действует.
   Правила не удалены сознательно: они безвредны, а их наличие не даёт
   развалиться раскладке, если кто-то вернёт float отдельным правилом. */

/* Во время правки плашка действий не нужна: сообщение сейчас редактируется,
   а кнопки копирования, цитаты и удаления висят прямо над полем ввода и
   перекрывают текст. Признак — форма правки внутри пузыря; :has() выбран,
   чтобы не заводить класс-состояние и не искать все выходы из режима правки
   (отмена, сохранение, Esc, клик по другому сообщению): любой из них удаляет
   форму, и плашка возвращается сама. В style.css :has() уже используется. */
.message-bubble:has(.edit-form-wrapper) .msg-actions {
    display: none;
}

/* ---------- Кружок ожидания при сохранении правки -------------------------
   В style.css он спозиционирован абсолютом от пузыря: top:7px, left:50% —
   то есть по центру верхней части сообщения. Это работало, пока там стояла
   подпись автора: кружок ложился на неё, на пустую по сути строку. Подпись
   в диалоге скрыта, и он оказался поверх первой строки текста, сливаясь с ней.

   Вставляется он в подвал, перед временем (chat-dialog.js), поэтому проще и
   честнее не искать ему новое место абсолютом, а оставить в потоке: рядом со
   временем есть место, а строка подвала не изменит высоту — кружок ровно
   такой же, как отступ строки. margin-left:auto прижимает его к времени и в
   случае, когда в подвале есть ещё и реакции. */
.msg-footer .message-pending-spinner {
    position: static;
    flex: 0 0 auto;
    width: 12px;
    height: 12px;
    margin-left: auto;
    margin-right: 5px;
}

/* На узком экране style.css сжимает бейдж обратно до 18px и задаёт
   font-size:16px — цифра в такой коробке не помещалась и обрезалась.
   Возвращаем ту же геометрию, что на десктопе: кнопка на телефоне лишь
   немного меньше (52px против 56), уменьшать бейдж вслед за ней незачем. */
@media (max-width: 768px) {
    .dialogs-fab-badge {
        font-size: var(--fs-sm);
        line-height: 21px;
        min-width: 21px;
        height: 21px;
    }
}

/* =========================================================================
   Карточка знакомства в пустом диалоге

   Разметку отдаёт сервер (#dialog-intro-tpl в chat.php), подставляет её
   chat_main.renderEmptyDialog. Класс .no-messages на ней сохранён — по нему
   заглушку убирает первое же сообщение, — но весь вид прежней строчки
   (пунктирная рамка, курсив, ширина 275px) здесь переопределяется: у карточки
   другая задача.

   Смысл в том, чтобы человек, открывший переписку с незнакомым собеседником,
   увидел то, что тот о себе написал, не уходя в профиль. Поэтому порядок
   такой: аватар, имя, описание, теги — и только потом подсказка о том, что
   переписки ещё не было.
   ========================================================================= */

.dialog-intro {
    /* Перебиваем оформление .no-messages из style.css. */
    border: 1px solid var(--hairline);
    border-radius: var(--r-lg);
    background: var(--msg-bg);
    box-shadow: var(--e1);
    max-width: 420px;
    /* margin:auto по всем сторонам центрирует карточку в пустой ленте: та
       переведена на flex-колонку, и свободное место делится поровну сверху и
       снизу. Прижатая к верху карточка читалась как оборвавшийся список, а не
       как оформленное пустое состояние.
       На низком экране обрезки не будет: во flex автоматические поля при
       нехватке места считаются нулевыми, и карточка просто прокручивается. */
    margin: auto;
    padding: 22px 20px 18px;
    font-style: normal;
    font-size: var(--fs-md);
    color: var(--text);

    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    animation: dialogIntroIn .32s var(--ease) both;
}

@keyframes dialogIntroIn {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
}

.dialog-intro__ava {
    width: 72px;
    height: 72px;
    border-radius: var(--r-full);
    overflow: hidden;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--brand-soft);
    text-decoration: none;
    transition: transform var(--dur) var(--ease);
}

@media (hover: hover) {
    .dialog-intro__ava:hover { transform: scale(1.04); }
}

.dialog-intro__ava img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.dialog-intro__letter {
    font-size: var(--fs-xl);
    font-weight: 700;
    color: var(--brand);
}

.dialog-intro__name {
    font-size: var(--fs-lg);
    font-weight: 700;
    line-height: 1.2;
    text-align: center;
    word-break: break-word;
}

/* Описание пишет пользователь: и перенос строк, и очень длинное слово без
   пробелов — обычное дело. Ограничение по числу строк держит карточку
   компактной, полный текст остаётся в профиле. */
.dialog-intro__bio {
    margin: 0;
    text-align: center;
    font-size: var(--fs-sm);
    line-height: 1.45;
    color: var(--muted);
    word-break: break-word;
    display: -webkit-box;
    -webkit-line-clamp: 5;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.dialog-intro__tags {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 6px;
}

.dialog-intro__tag {
    padding: 3px 10px;
    border-radius: var(--r-full);
    background: var(--brand-soft);
    border: 1px solid var(--brand-fill);
    color: var(--brand);
    font-size: var(--fs-2xs);
    font-weight: 600;
    white-space: nowrap;
}

/* Подсказка отделена чертой: выше — про собеседника, ниже — про то, что
   делать дальше. */
.dialog-intro__hint {
    margin: 4px 0 0;
    padding-top: 12px;
    width: 100%;
    border-top: 1px solid var(--hairline);
    text-align: center;
    font-size: var(--fs-xs);
    color: var(--muted);
}

@media (max-width: 768px) {
    .dialog-intro {
        max-width: none;
        margin: 16px 8px;
        padding: 18px 16px 14px;
    }

    .dialog-intro__ava {
        width: 60px;
        height: 60px;
    }
}

@media (prefers-reduced-motion: reduce) {
    .dialog-intro { animation: none; }
}

/* Имя и теги в карточке стали ссылками — снимаем стандартное подчёркивание и
   даём отклик на наведение. Цвет имени наследуется от карточки: подкрашивать
   его как ссылку незачем, это заголовок, а не действие в тексте. */
.dialog-intro__name {
    color: inherit;
    text-decoration: none;
    transition: color var(--dur) var(--ease);
}

.dialog-intro__tag {
    text-decoration: none;
    transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

@media (hover: hover) {
    .dialog-intro__name:hover {
        color: var(--brand);
    }

    .dialog-intro__tag:hover {
        background: var(--brand-fill);
        border-color: var(--brand);
    }
}
