:root {
    /* === Поверхности === */
    --bg: #eef2f7;
    --panel: #ffffff;
    --panel-alt: #f4f6f8;
    --panel-alt-border: #e2e8f0;

    /*
     * v58.35 (этап 2, список чатов и шапка): в оригинальном дизайне
     * шапка (.sidebar-header/.conversation-header) была тёмно-синей
     * (#0f172a) с белым текстом - это "modern SaaS"-паттерн, а не
     * Telegram. Настоящий Telegram (iOS/Desktop, светлая тема) держит
     * шапку БЕЛОЙ/светлой с тёмным текстом и синими (акцентными)
     * иконками - тонкая граница снизу отделяет её от контента вместо
     * контраста по заливке. См. --header-text/--header-icon/
     * --header-border ниже и правки .sidebar-header/.conversation-header/
     * .icon-button/.back-button/.group-notification-button дальше по
     * файлу. В тёмной теме шапка остаётся тёмной (см. dark-блок ниже) -
     * это КАК РАЗ совпадает с настоящим Telegram Night, там смены не
     * потребовалось.
     */
    --header: #ffffff;
    --header-soft: #1e293b;
    --header-text: #172033;
    --header-icon: var(--accent);
    --header-border: var(--border);

    /* === Текст === */
    --text: #172033;
    --header-text-muted: #64748b;
    --muted: #758199;
    --muted-2: #64748b;
    --muted-3: #94a3b8;
    --muted-strong: #334155;

    /*
     * Текст/иконки поверх ЗАЛИТЫХ акцентным цветом поверхностей
     * (badge, отправка, аватар-заглушка, лого) - остаётся белым в
     * обеих темах (см. dark-блок ниже - то же значение), поэтому не
     * "плывёт" вместе с --text/--header-text при смене темы.
     */
    --on-accent: #ffffff;

    /* === Границы === */
    --border: #e4e8ef;
    --input-border: #d7dee8;
    --input-bg: #ffffff;

    /*
     * v58.35: акцент сменён с #2563eb (Tailwind blue-600, "modern
     * SaaS") на #3390ec - узнаваемый синий Telegram (кнопка отправки,
     * ссылки, непрочитанный badge в реальном Telegram-клиенте). Токен
     * один - значение меняется здесь один раз и автоматически
     * подхватывается ВСЕМИ уже стилизованными элементами по всему
     * файлу (badge, ссылки, рамка цитаты, фокус полей и т.д.), без
     * точечных правок - в этом и была цель токенизации на этапе 1.
     */
    --accent: #3390ec;
    --accent-hover: #2b7fcb;
    --accent-2: #0ea5e9;
    --accent-light: #3b82f6;
    --accent-rgb: 51, 144, 236;
    --accent-2-rgb: 14, 165, 233;
    --accent-light-rgb: 59, 130, 246;
    --accent-soft-rgb: 239, 246, 255;

    /* === Пузыри сообщений === */
    --incoming: #ffffff;
    --outgoing: #dff3ff;

    /* === Статусы === */
    --danger: #dc2626;
    --danger-strong: #b91c1c;
    --danger-light: #fca5a5;
    --danger-rgb: 220, 38, 38;
    --danger-soft-rgb: 254, 242, 242;

    --success: #22c55e;
    --success-strong: #15803d;
    --success-rgb: 34, 197, 94;
    --success-soft-rgb: 240, 253, 244;

    --warning: #f59e0b;
    --warning-strong: #d97706;
    --warning-light: #fbbf24;

    --info: #1687e8;

    /* === Служебные === */
    --toast-bg: #111827;
    --shadow: 0 12px 38px rgba(15, 23, 42, 0.12);
    --shadow-rgb: 15, 23, 42;
    --surface-rgb: 255, 255, 255;
    --border-tint-rgb: 148, 163, 184;
    --panel-alt-border-rgb: 226, 232, 240;

    /*
     * v58.36 (этап 3, пузыри и цитаты): прозрачность фоновой заливки
     * .message-quote (accent-rgb, alpha). В светлой теме на бледном
     * --outgoing (#dff3ff) заливка на 0.08 даёт разницу ~14-16 RGB-
     * пунктов от фона пузыря - достаточно заметно. В тёмной теме на
     * более насыщенном --outgoing (#2b5278) та же alpha=0.08 давала
     * разницу ВСЕГО ~4-10 RGB-пунктов - цитата почти сливалась с
     * пузырём (левая граница-акцент оставалась заметной, но фоновая
     * подложка - нет). Отдельный токен на тему решает это точечно, не
     * трогая сам --accent-rgb.
     */
    --quote-bg-alpha: 0.08;
    --muted-2-rgb: 100, 116, 139;

    /*
     * v58.37 (этап 4, композер/меню/forward-picker): найдена в
     * .message-menu-preview - "стеклянный" верхний блик карточки
     * предпросмотра сообщения в меню действий (inset box-shadow,
     * имитирует глянцевую подсветку верхнего края) был на
     * rgba(var(--surface-rgb), 0.75). --surface-rgb ОТСЛЕЖИВАЕТ тему
     * (белый в светлой / тёмная панель в тёмной) - в светлой теме это
     * даёт белый блик поверх белой же карточки (заметно), но в тёмной
     * теме получается тёмный "блик" поверх тёмной же карточки того же
     * оттенка - визуально не читается вообще, эффект стекла пропадает.
     * Блик должен быть светлым НЕЗАВИСИМО от темы (как и --on-accent) -
     * отдельный theme-invariant токен вместо --surface-rgb.
     */
    --glass-highlight-rgb: 255, 255, 255;

    /*
     * v58.35: тон для полупрозрачных :active/:hover подсветок ИМЕННО
     * на элементах шапки (иконки) - в отличие от --surface-rgb (общий
     * "стеклянный" тон панелей, который в тёмной теме уходит в тёмный,
     * см. .push-bell-button и т.п.), этот токен должен КОНТРАСТИРОВАТЬ
     * с фоном самой шапки: в светлой теме шапка светлая -> нужен
     * затемняющий тон; в тёмной теме шапка тёмная -> нужен
     * осветляющий тон (см. dark-блок ниже - там триплет инвертирован).
     */
    --ink-tint-rgb: 15, 23, 42;
}

/*
 * v58.34: тёмная тема (Telegram Night-подобная палитра). Пока только
 * по системной настройке устройства (prefers-color-scheme) - ручной
 * тумблер внутри приложения добавится отдельным этапом (переопределит
 * эти же токены через [data-theme="dark"], без изменения значений
 * ниже). Токены здесь ТЕ ЖЕ САМЫЕ имена, что и в светлой теме выше -
 * весь остальной styles.css ссылается на них через var(), поэтому
 * переопределение работает для каждого уже стилизованного компонента
 * автоматически, без точечных правок по всему файлу.
 */
@media (prefers-color-scheme: dark) {
    :root {
        --bg: #0e1621;
        --panel: #17212b;
        --panel-alt: #1c2733;
        --panel-alt-border: #24313d;
        --header: #17212b;
        --header-soft: #202b36;

        /*
         * v58.35: тёмная шапка не меняется (уже совпадает с настоящим
         * Telegram Night) - но новые токены header-text/header-icon/
         * header-border должны быть переопределены здесь явно, иначе
         * они бы "протекли" из светлого :root (белая шапка) и в тёмной
         * теме шапка осталась бы с тёмным текстом на тёмном фоне -
         * нечитаемо. Граница у header-border ниже намеренно прозрачная -
         * в тёмной теме контраст шапки уже даёт заливка (#17212b на фоне
         * #0e1621), отдельная граница не нужна (так и было в оригинале
         * до этапа 2).
         */
        --header-text: #ffffff;
        --header-icon: #ffffff;
        --header-border: transparent;

        --text: #ffffff;
        --header-text-muted: #708499;
        --muted: #82909e;
        --muted-2: #8a97a3;
        --muted-3: #64758a;
        --muted-strong: #c3ccd6;

        /* Белый текст/иконки поверх залитых акцентом поверхностей - то же значение, что и в светлой теме (см. комментарий там). */
        --on-accent: #ffffff;

        --border: #1e2c39;
        --input-border: #2b3a48;
        --input-bg: #131c26;

        /*
         * v58.35: хue нового светлого акцента (#3390ec, ~210°) почти
         * совпадает с уже существующим тёмным акцентом (#5eabf5, ~209°) -
         * ретюнинг не понадобился, значения ниже не менялись с v58.34.
         */
        --accent: #5eabf5;
        --accent-hover: #4a9eea;
        --accent-2: #4fc3e8;
        --accent-light: #6bb6ff;
        --accent-rgb: 94, 171, 245;
        --accent-2-rgb: 79, 195, 232;
        --accent-light-rgb: 107, 182, 255;
        --accent-soft-rgb: 20, 35, 54;

        --incoming: #182533;
        --outgoing: #2b5278;

        /*
         * v58.36: в тёмной теме заливка .message-quote с alpha=0.08
         * (как в светлой) почти сливалась с более насыщенным
         * --outgoing (#2b5278) - разница фона составляла всего
         * ~4-10 RGB-пунктов вместо ~14-16 в светлой теме. Удваиваем
         * alpha, чтобы подложка цитаты была заметна так же, как в
         * светлой теме (левая граница-акцент и так была видна).
         */
        --quote-bg-alpha: 0.16;

        --danger: #ef5350;
        --danger-strong: #ff6b6b;
        --danger-light: #ff8a8a;
        --danger-rgb: 239, 83, 80;
        --danger-soft-rgb: 58, 30, 30;

        --success: #58d66c;
        --success-strong: #34c364;
        --success-rgb: 88, 214, 108;
        --success-soft-rgb: 20, 42, 28;

        --warning: #ffb545;
        --warning-strong: #e2952e;
        --warning-light: #ffcc70;

        --info: #5eabf5;

        --toast-bg: #0b0f16;
        --shadow: 0 12px 38px rgba(0, 0, 0, 0.42);
        --shadow-rgb: 0, 0, 0;
        --surface-rgb: 23, 33, 43;
        --border-tint-rgb: 255, 255, 255;
        --panel-alt-border-rgb: 36, 49, 61;
        --muted-2-rgb: 130, 150, 168;

        /*
         * v58.37: "стеклянный" блик - theme-invariant, то же значение,
         * что и в светлой теме (см. комментарий там). Именно ЕГО
         * отсутствие здесь (раньше вместо него use --surface-rgb, тёмный
         * в этой теме) делало верхний блик карточки .message-menu-
         * preview невидимым в тёмной теме.
         */
        --glass-highlight-rgb: 255, 255, 255;

        /*
         * v58.35: инверсия относительно светлой темы (15, 23, 42) - в
         * тёмной теме шапка тёмная, значит :active/:hover подсветка
         * иконок шапки должна ОСВЕТЛЯТЬ, а не затемнять (см. комментарий
         * у светлого --ink-tint-rgb выше по файлу).
         */
        --ink-tint-rgb: 255, 255, 255;
    }
}

/*
 * v58.38 (этап 5, тумблер темы): явный выбор пользователя через
 * [data-theme] на <html> (см. themeToggleButton в index.html/app.js
 * и синхронный inline-скрипт в <head> - выставляет атрибут ДО первой
 * отрисовки, чтобы не было вспышки не той темы). Специфичность
 * ":root[data-theme=...]" (0,2,0) ВЫШЕ, чем ":root" внутри
 * "@media (prefers-color-scheme: dark)" (0,1,0) - поэтому явный выбор
 * всегда побеждает системную настройку, независимо от порядка в
 * файле. Значения ниже СГЕНЕРИРОВАНЫ из уже существующих блоков выше
 * (сборкой этого шага) - побайтово совпадают с обычными :root и
 * dark-медиа-блоком, отдельно поддерживать три копии не нужно: любое
 * будущее изменение токена в исходных блоках должно быть продублировано
 * и сюда вручную (перегенерировать этим же скриптом при следующей
 * правке палитры).
 */
:root[data-theme="light"] {
    /* === Поверхности === */
    --bg: #eef2f7;
    --panel: #ffffff;
    --panel-alt: #f4f6f8;
    --panel-alt-border: #e2e8f0;

    /*
     * v58.35 (этап 2, список чатов и шапка): в оригинальном дизайне
     * шапка (.sidebar-header/.conversation-header) была тёмно-синей
     * (#0f172a) с белым текстом - это "modern SaaS"-паттерн, а не
     * Telegram. Настоящий Telegram (iOS/Desktop, светлая тема) держит
     * шапку БЕЛОЙ/светлой с тёмным текстом и синими (акцентными)
     * иконками - тонкая граница снизу отделяет её от контента вместо
     * контраста по заливке. См. --header-text/--header-icon/
     * --header-border ниже и правки .sidebar-header/.conversation-header/
     * .icon-button/.back-button/.group-notification-button дальше по
     * файлу. В тёмной теме шапка остаётся тёмной (см. dark-блок ниже) -
     * это КАК РАЗ совпадает с настоящим Telegram Night, там смены не
     * потребовалось.
     */
    --header: #ffffff;
    --header-soft: #1e293b;
    --header-text: #172033;
    --header-icon: var(--accent);
    --header-border: var(--border);

    /* === Текст === */
    --text: #172033;
    --header-text-muted: #64748b;
    --muted: #758199;
    --muted-2: #64748b;
    --muted-3: #94a3b8;
    --muted-strong: #334155;

    /*
     * Текст/иконки поверх ЗАЛИТЫХ акцентным цветом поверхностей
     * (badge, отправка, аватар-заглушка, лого) - остаётся белым в
     * обеих темах (см. dark-блок ниже - то же значение), поэтому не
     * "плывёт" вместе с --text/--header-text при смене темы.
     */
    --on-accent: #ffffff;

    /* === Границы === */
    --border: #e4e8ef;
    --input-border: #d7dee8;
    --input-bg: #ffffff;

    /*
     * v58.35: акцент сменён с #2563eb (Tailwind blue-600, "modern
     * SaaS") на #3390ec - узнаваемый синий Telegram (кнопка отправки,
     * ссылки, непрочитанный badge в реальном Telegram-клиенте). Токен
     * один - значение меняется здесь один раз и автоматически
     * подхватывается ВСЕМИ уже стилизованными элементами по всему
     * файлу (badge, ссылки, рамка цитаты, фокус полей и т.д.), без
     * точечных правок - в этом и была цель токенизации на этапе 1.
     */
    --accent: #3390ec;
    --accent-hover: #2b7fcb;
    --accent-2: #0ea5e9;
    --accent-light: #3b82f6;
    --accent-rgb: 51, 144, 236;
    --accent-2-rgb: 14, 165, 233;
    --accent-light-rgb: 59, 130, 246;
    --accent-soft-rgb: 239, 246, 255;

    /* === Пузыри сообщений === */
    --incoming: #ffffff;
    --outgoing: #dff3ff;

    /* === Статусы === */
    --danger: #dc2626;
    --danger-strong: #b91c1c;
    --danger-light: #fca5a5;
    --danger-rgb: 220, 38, 38;
    --danger-soft-rgb: 254, 242, 242;

    --success: #22c55e;
    --success-strong: #15803d;
    --success-rgb: 34, 197, 94;
    --success-soft-rgb: 240, 253, 244;

    --warning: #f59e0b;
    --warning-strong: #d97706;
    --warning-light: #fbbf24;

    --info: #1687e8;

    /* === Служебные === */
    --toast-bg: #111827;
    --shadow: 0 12px 38px rgba(15, 23, 42, 0.12);
    --shadow-rgb: 15, 23, 42;
    --surface-rgb: 255, 255, 255;
    --border-tint-rgb: 148, 163, 184;
    --panel-alt-border-rgb: 226, 232, 240;

    /*
     * v58.36 (этап 3, пузыри и цитаты): прозрачность фоновой заливки
     * .message-quote (accent-rgb, alpha). В светлой теме на бледном
     * --outgoing (#dff3ff) заливка на 0.08 даёт разницу ~14-16 RGB-
     * пунктов от фона пузыря - достаточно заметно. В тёмной теме на
     * более насыщенном --outgoing (#2b5278) та же alpha=0.08 давала
     * разницу ВСЕГО ~4-10 RGB-пунктов - цитата почти сливалась с
     * пузырём (левая граница-акцент оставалась заметной, но фоновая
     * подложка - нет). Отдельный токен на тему решает это точечно, не
     * трогая сам --accent-rgb.
     */
    --quote-bg-alpha: 0.08;
    --muted-2-rgb: 100, 116, 139;

    /*
     * v58.37 (этап 4, композер/меню/forward-picker): найдена в
     * .message-menu-preview - "стеклянный" верхний блик карточки
     * предпросмотра сообщения в меню действий (inset box-shadow,
     * имитирует глянцевую подсветку верхнего края) был на
     * rgba(var(--surface-rgb), 0.75). --surface-rgb ОТСЛЕЖИВАЕТ тему
     * (белый в светлой / тёмная панель в тёмной) - в светлой теме это
     * даёт белый блик поверх белой же карточки (заметно), но в тёмной
     * теме получается тёмный "блик" поверх тёмной же карточки того же
     * оттенка - визуально не читается вообще, эффект стекла пропадает.
     * Блик должен быть светлым НЕЗАВИСИМО от темы (как и --on-accent) -
     * отдельный theme-invariant токен вместо --surface-rgb.
     */
    --glass-highlight-rgb: 255, 255, 255;

    /*
     * v58.35: тон для полупрозрачных :active/:hover подсветок ИМЕННО
     * на элементах шапки (иконки) - в отличие от --surface-rgb (общий
     * "стеклянный" тон панелей, который в тёмной теме уходит в тёмный,
     * см. .push-bell-button и т.п.), этот токен должен КОНТРАСТИРОВАТЬ
     * с фоном самой шапки: в светлой теме шапка светлая -> нужен
     * затемняющий тон; в тёмной теме шапка тёмная -> нужен
     * осветляющий тон (см. dark-блок ниже - там триплет инвертирован).
     */
    --ink-tint-rgb: 15, 23, 42;

}

:root[data-theme="dark"] {
    --bg: #0e1621;
    --panel: #17212b;
    --panel-alt: #1c2733;
    --panel-alt-border: #24313d;
    --header: #17212b;
    --header-soft: #202b36;

    /*
     * v58.35: тёмная шапка не меняется (уже совпадает с настоящим
     * Telegram Night) - но новые токены header-text/header-icon/
     * header-border должны быть переопределены здесь явно, иначе
     * они бы "протекли" из светлого :root (белая шапка) и в тёмной
     * теме шапка осталась бы с тёмным текстом на тёмном фоне -
     * нечитаемо. Граница у header-border ниже намеренно прозрачная -
     * в тёмной теме контраст шапки уже даёт заливка (#17212b на фоне
     * #0e1621), отдельная граница не нужна (так и было в оригинале
     * до этапа 2).
     */
    --header-text: #ffffff;
    --header-icon: #ffffff;
    --header-border: transparent;

    --text: #ffffff;
    --header-text-muted: #708499;
    --muted: #82909e;
    --muted-2: #8a97a3;
    --muted-3: #64758a;
    --muted-strong: #c3ccd6;

    /* Белый текст/иконки поверх залитых акцентом поверхностей - то же значение, что и в светлой теме (см. комментарий там). */
    --on-accent: #ffffff;

    --border: #1e2c39;
    --input-border: #2b3a48;
    --input-bg: #131c26;

    /*
     * v58.35: хue нового светлого акцента (#3390ec, ~210°) почти
     * совпадает с уже существующим тёмным акцентом (#5eabf5, ~209°) -
     * ретюнинг не понадобился, значения ниже не менялись с v58.34.
     */
    --accent: #5eabf5;
    --accent-hover: #4a9eea;
    --accent-2: #4fc3e8;
    --accent-light: #6bb6ff;
    --accent-rgb: 94, 171, 245;
    --accent-2-rgb: 79, 195, 232;
    --accent-light-rgb: 107, 182, 255;
    --accent-soft-rgb: 20, 35, 54;

    --incoming: #182533;
    --outgoing: #2b5278;

    /*
     * v58.36: в тёмной теме заливка .message-quote с alpha=0.08
     * (как в светлой) почти сливалась с более насыщенным
     * --outgoing (#2b5278) - разница фона составляла всего
     * ~4-10 RGB-пунктов вместо ~14-16 в светлой теме. Удваиваем
     * alpha, чтобы подложка цитаты была заметна так же, как в
     * светлой теме (левая граница-акцент и так была видна).
     */
    --quote-bg-alpha: 0.16;

    --danger: #ef5350;
    --danger-strong: #ff6b6b;
    --danger-light: #ff8a8a;
    --danger-rgb: 239, 83, 80;
    --danger-soft-rgb: 58, 30, 30;

    --success: #58d66c;
    --success-strong: #34c364;
    --success-rgb: 88, 214, 108;
    --success-soft-rgb: 20, 42, 28;

    --warning: #ffb545;
    --warning-strong: #e2952e;
    --warning-light: #ffcc70;

    --info: #5eabf5;

    --toast-bg: #0b0f16;
    --shadow: 0 12px 38px rgba(0, 0, 0, 0.42);
    --shadow-rgb: 0, 0, 0;
    --surface-rgb: 23, 33, 43;
    --border-tint-rgb: 255, 255, 255;
    --panel-alt-border-rgb: 36, 49, 61;
    --muted-2-rgb: 130, 150, 168;

    /*
     * v58.37: "стеклянный" блик - theme-invariant, то же значение,
     * что и в светлой теме (см. комментарий там). Именно ЕГО
     * отсутствие здесь (раньше вместо него use --surface-rgb, тёмный
     * в этой теме) делало верхний блик карточки .message-menu-
     * preview невидимым в тёмной теме.
     */
    --glass-highlight-rgb: 255, 255, 255;

    /*
     * v58.35: инверсия относительно светлой темы (15, 23, 42) - в
     * тёмной теме шапка тёмная, значит :active/:hover подсветка
     * иконок шапки должна ОСВЕТЛЯТЬ, а не затемнять (см. комментарий
     * у светлого --ink-tint-rgb выше по файлу).
     */
    --ink-tint-rgb: 255, 255, 255;
}

* {
    box-sizing: border-box;
}

html,
body {
    width: 100%;
    height: 100%;
    margin: 0;
}

body {
    overflow: hidden;
    background: var(--bg);
    color: var(--text);
    font-family:
        -apple-system,
        BlinkMacSystemFont,
        "SF Pro Text",
        "Segoe UI",
        sans-serif;
    -webkit-font-smoothing: antialiased;
}

button,
textarea {
    font: inherit;
}

button {
    -webkit-tap-highlight-color: transparent;
}

.hidden {
    display: none !important;
}

.app-shell {
    display: grid;
    grid-template-columns: 360px minmax(0, 1fr);
    width: 100%;
    height: 100svh;
    background: var(--panel);
}

.sidebar {
    /*
     * v58.49: position:relative добавлен как positioning-контекст для
     * #bottomTabBar (position:absolute, bottom:0) - таб-бар должен
     * лежать ПОВЕРХ нижней safe-area-полосы, а не в обычном потоке
     * после списка чатов (см. .bottom-tab-bar ниже за подробным
     * разбором бага, из-за которого пользователь пожаловался на
     * скриншоте - кнопки оказались НАД серой полосой, а не в ней).
     */
    position: relative;
    min-width: 0;
    border-right: 1px solid var(--border);
    background: var(--panel);
}

.sidebar-header,
.conversation-header {
    display: flex;
    align-items: center;
    /*
     * v60.23: 72px -> 64px. Естественная высота содержимого (заголовок
     * "iChat" + статус подключения, либо аватар/имя/статус диалога)
     * укладывается в ~62-68px - min-height:72px добавлял лишние
     * несколько пикселей пустого места сверху и снизу шапки списка
     * чатов (жалоба "слишком высокая шапка... много пустого
     * пространства"). CHAT_SEARCH_COLLAPSED_OFFSET/
     * CHAT_SEARCH_EXPANDED_OFFSET в app.js уменьшены на те же 8px.
     */
    min-height: 64px;
    background: var(--header);
    color: var(--header-text);
}

.sidebar-header {
    justify-content: space-between;
    padding:
        calc(9px + env(safe-area-inset-top))
        16px
        9px;
}

.brand {
    font-size: 22px;
    font-weight: 750;
    letter-spacing: -0.5px;
}

.connection-status {
    margin-top: 3px;
    color: var(--header-text-muted);
    font-size: 12px;
}

.icon-button,
.back-button {
    border: 0;
    background: transparent;
    color: var(--header-icon);
    cursor: pointer;
    /* v60.21: единая лёгкая анимация нажатия для всех круглых кнопок
       шапки - раньше подсветка появлялась/пропадала мгновенно, теперь
       плавно, плюс небольшое сжатие (см. transform в :active ниже). */
    transition: background 120ms ease, transform 120ms ease;
}

.icon-button {
    /* v60.15: 42px -> 44px - единый iOS tap-target-минимум для всех
       круглых icon-button (аудит консистентности кнопок), см.
       docs/04-visual-sizing-telegram-comparison.md. */
    width: 44px;
    height: 44px;
    border-radius: 50%;
    font-size: 27px;
}

/*
 * v58.35: --ink-tint-rgb, а не --surface-rgb - подсветка должна
 * контрастировать с фоном ШАПКИ, а не следовать общему "стеклянному"
 * тону панелей (см. комментарий у --ink-tint-rgb в :root).
 * v60.21: .back-button добавлена в эту же подсветку + едва заметное
 * сжатие (scale 0.93) - раньше у кнопки "назад" вообще не было
 * никакого отклика на нажатие.
 */
.icon-button:active,
.back-button:active {
    background: rgba(var(--ink-tint-rgb), 0.12);
    transform: scale(0.93);
}

/*
 * v60.22: .back-button и .conversation-overflow-button теперь имеют
 * видимый фон в покое (см. правки выше), поэтому на нажатии им нужен
 * не плоский ink-tint (это выглядело бы как "затирание" градиента), а
 * тот же приём, что уже используют .push-bell-button/
 * .sidebar-settings-button - лёгкий акцентный оттенок. Идёт после
 * общего .icon-button:active, чтобы выиграть по каскаду для этих
 * двух конкретных кнопок.
 */
.back-button:active,
.conversation-overflow-button:active {
    background: rgba(var(--accent-soft-rgb), 0.98);
}

/*
 * v60.29: SVG-стрелка внутри .back-button (см. index.html и
 * комментарий там) - фиксированный размер, цвет наследуется через
 * currentColor (тот же color, что раньше применялся к текстовому
 * глифу).
 */
.back-button-icon {
    width: 22px;
    height: 22px;
    fill: none;
    stroke: currentColor;
    stroke-width: 2.2;
    stroke-linecap: round;
    stroke-linejoin: round;
    /*
     * v60.32: у ::before-капсулы (см. ниже) тоже будет position:absolute
     * с z-index:0 - без этого позиционирование стрелка/капсула
     * рисовались бы в порядке "как в разметке", и абсолютно
     * спозиционированная капсула перекрыла бы статичную стрелку.
     * position:relative делает стрелку тоже участником позиционного
     * слоя, z-index:1 гарантирует, что она всегда поверх капсулы.
     */
    position: relative;
    z-index: 1;
}

/*
 * v60.29: по просьбе пользователя - кнопка "назад" при нажатии
 * должна слегка УВЕЛИЧИВАТЬСЯ, а не сжиматься (в отличие от всех
 * остальных .icon-button, которые используют общий scale(0.93) из
 * правила выше). Указано ПОСЛЕ общего .icon-button:active,
 * .back-button:active { ...; transform: scale(0.93); } с той же
 * специфичностью (один класс + один псевдокласс) - выигрывает по
 * порядку в каскаде. scale(1.08) - тот же не "перегруженный" масштаб,
 * что и pop-эффект капсулы вкладок (см. .chat-folder-tabs-indicator),
 * для единого визуального языка новых анимаций этой версии.
 */
.back-button:active {
    transform: scale(1.08);
}

/*
 * v60.32: жалоба пользователя - "не вижу анимации на кнопке назад".
 * Диагностика по коду: (1) сам scale(1.08) выше даёт всего ~3.5px
 * роста у круга 44px - реального увеличения размера часто
 * недостаточно, чтобы это прочиталось как "анимация", особенно на
 * быстром тапе; (2) у .back-button (как и почти у всех icon-button в
 * шапке) только click-обработчик в app.js, без touchstart/pointerdown
 * ни на самой кнопке, ни на каком-либо предке вплоть до document -
 * классический WebKit-квирк: iOS Safari не считает нужным
 * рендерить :active-стили по тапу, если touchstart нигде на пути нет
 * (см. также глобальный touchstart-шим в app.js, который чинит это
 * системно для всех кнопок).
 *
 * Вместо того чтобы полагаться на едва заметный scale, добавлена
 * отдельная "стеклянная капсула" - полупрозрачный радиальный блик
 * ::before, который по нажатию раздувается из центра и слегка
 * перелетает (та же кривая, что и у pop-эффекта капсулы вкладок) -
 * ровно то, что пользователь описывал изначально ("капсула прозрачная
 * появляется на кнопке, обволакивает её за края"). border-radius:50%
 * на самом псевдоэлементе (а не overflow:hidden на кнопке) - чтобы не
 * обрезать собственную тень кнопки в состоянии покоя.
 */
.back-button::before {
    content: '';
    position: absolute;
    inset: -3px;
    z-index: 0;
    border-radius: 50%;
    background: radial-gradient(
        circle,
        rgba(var(--accent-rgb), 0.4),
        rgba(var(--accent-rgb), 0.16) 65%,
        rgba(var(--accent-rgb), 0) 100%
    );
    opacity: 0;
    transform: scale(0.55);
    transition:
        opacity 160ms ease,
        transform 320ms cubic-bezier(0.34, 1.56, 0.64, 1);
    pointer-events: none;
}

.back-button:active::before {
    opacity: 1;
    transform: scale(1.1);
}

@media (prefers-reduced-motion: reduce) {
    .back-button::before {
        transition-duration: 1ms;
    }
}

.chat-search {
    display: flex;
    align-items: center;
    gap: 6px;
    overflow: hidden;
    max-height: 64px;
    padding: 6px 14px 10px;
    background: var(--panel);

    /*
     * v58.14: плавное скрытие/появление при скролле списка
     * чатов (см. handleChatListScrollForSearchBar в app.js) -
     * вместо статично зафиксированной строки поиска.
     */
    transition:
        max-height 260ms ease,
        padding 260ms ease,
        opacity 200ms ease,
        transform 200ms ease;
}

.chat-search.chat-search-collapsed {
    max-height: 0;
    padding-top: 0;
    padding-bottom: 0;
    opacity: 0;
    transform: translateY(-6px);
}

.chat-search-field {
    position: relative;
    flex: 1;
    min-width: 0;
}

.chat-search-icon {
    position: absolute;
    top: 50%;
    left: 14px;
    width: 18px;
    height: 18px;
    transform: translateY(-50%);
    fill: none;
    stroke: var(--muted);
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-width: 2;
    pointer-events: none;
}

.chat-search-input {
    width: 100%;
    /* v60.15: 40px -> 44px - стандартный iOS tap-target, заодно
       ближе к тому, как выглядит поле поиска в Telegram. */
    height: 44px;
    padding: 0 14px 0 40px;
    border: 1px solid var(--input-border);
    border-radius: 22px;
    outline: none;
    background: var(--input-bg);
    color: var(--text);
    /* 16px - обязательный минимум, иначе Safari на iPhone
       делает zoom страницы при фокусе на поле. */
    font-size: 16px;
}

.chat-search-input:focus {
    border-color: var(--accent);
}

.chat-search-clear {
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    /* Зона нажатия минимум 44x44px, сама иконка компактнее. */
    width: 44px;
    height: 44px;
    border-radius: 50%;
    font-size: 16px;
    color: var(--muted);

    /*
     * v58.13: плавное появление/скрытие вместо мгновенного
     * display:none (.hidden). Кнопка всегда занимает место в
     * layout (flex: 0 0 auto), меняется только её видимость -
     * соседний .chat-search-field ничего не "прыгает".
     */
    opacity: 0;
    transform: scale(0.7);
    transition: opacity 140ms ease, transform 140ms ease;
    pointer-events: none;
}

.chat-search-clear.visible {
    opacity: 1;
    transform: scale(1);
    pointer-events: auto;
}

.chat-search-clear:active {
    background: rgba(var(--shadow-rgb), 0.06);
}

/*
 * v58.46: вкладки папок (Все/Личные/Группы) над #chatList. Не
 * скрываются при скролле (в отличие от .chat-search) - см.
 * комментарий у #chatFolderTabs в index.html. Горизонтальный
 * overflow на случай мелких экранов/крупного текста - скрытый
 * скроллбар, чтобы не выглядело как отдельный виджет.
 */
.chat-folder-tabs {
    position: relative;
    display: flex;
    gap: 8px;
    padding: 6px 14px 10px;
    /* v60.21: сплошная var(--panel) -> полупрозрачная панель с блюром,
       как у .composer/.chat-search (см. rgba(var(--surface-rgb)...)
       ниже по файлу) - в стиле Telegram, вместо плоской заливки. */
    /* v60.39: убран backdrop-filter - пересчитывался на каждом кадре
       прокрутки списка чатов под панелью вкладок; фон почти
       непрозрачный, визуально разница минимальна. */
    background: rgba(var(--surface-rgb), 0.97);
    overflow-x: auto;
    scrollbar-width: none;
}

.chat-folder-tabs::-webkit-scrollbar {
    display: none;
}

/*
 * v60.28: скользящая стеклянная капсула - позиционируется JS
 * (positionChatFolderTabsIndicator() в app.js, offsetLeft/offsetWidth
 * активной кнопки -> transform/width индикатора), не CSS-переключением
 * класса. position:absolute внутри .chat-folder-tabs (которому для
 * этого добавлен position:relative выше) - оба измеряются и
 * позиционируются относительно одного и того же padding-box, включая
 * прокрутку (::-webkit-scrollbar скрыт, но overflow-x:auto остаётся
 * рабочим - индикатор как обычный child скроллится вместе с кнопками,
 * отдельная компенсация scrollLeft не нужна).
 *
 * top/bottom вместо height - все вкладки в один ряд одной высоты,
 * поэтому фиксированных отступов сверху/снизу достаточно и не нужно
 * измерять offsetHeight отдельно.
 */
.chat-folder-tabs-indicator {
    position: absolute;
    top: 6px;
    bottom: 10px;
    left: 0;
    width: 0;
    border-radius: 16px;
    background: linear-gradient(
        180deg,
        rgba(var(--accent-rgb), 0.98),
        rgba(var(--accent-rgb), 0.92)
    );
    /* v60.39: убран backdrop-filter - капсула ездит через JS-transform
       поверх прокручиваемых вкладок, блюр пересчитывался постоянно. */
    box-shadow:
        0 2px 8px rgba(var(--shadow-rgb), 0.14),
        inset 0 1px 0 rgba(255, 255, 255, 0.12);
    opacity: 0;
    pointer-events: none;
    z-index: 0;
    transform: translateX(0) scale(1);
    /*
     * v60.29: по просьбе пользователя анимация стала более заметной -
     * transform 320ms -> 420ms и линейная "приятная" кривая заменена
     * на cubic-bezier с лёгким перелётом (overshoot, ~1.09 в пике) -
     * капсула чуть-чуть "выстреливает" за целевую позицию и мягко
     * возвращается, а не просто плавно едет. Плюс JS (см.
     * positionChatFolderTabsIndicator) на каждое переключение вкладки
     * временно добавляет scale(1.08) поверх translateX и снимает его
     * с небольшой задержкой - тот же transform 420ms подхватывает и
     * это изменение, давая эффект "капсула слегка увеличивается", а
     * не просто едет. width оставлен на более мягкой кривой без
     * перелёта - "перелетающая" ширина визуально выглядела бы как
     * резиновое дрожание краёв, а не как упругость.
     *
     * v60.30: сама длительность/кривая здесь не менялась - хотфикс
     * дёрганого хвоста был в JS-таймере (см. positionChatFolderTabsIndicator),
     * который раньше перезаписывал transform ДО того, как этот 420ms
     * перелёт успевал долететь и остановиться.
     */
    transition:
        transform 420ms cubic-bezier(0.34, 1.56, 0.64, 1),
        width 320ms cubic-bezier(0.22, 1, 0.36, 1),
        opacity 140ms ease;
    will-change: transform, width;
}

/* v60.28: без анимации - для первой отрисовки/ресайза (см. JS), чтобы
   капсула не "приезжала" издалека при первой загрузке или повороте
   экрана. */
.chat-folder-tabs-indicator.no-transition {
    transition: none;
}

.chat-folder-tab {
    position: relative;
    /* v60.28: капсула-индикатор лежит в том же контейнере с
       z-index:0 - кнопки должны быть выше неё, иначе текст/фон кнопки
       перекроют капсулу вместо того, чтобы капсула была виднапод
       активной кнопкой. */
    z-index: 1;
    flex: 0 0 auto;
    padding: 7px 14px;
    border: 1px solid var(--border);
    border-radius: 16px;
    /*
     * v60.28: было сплошной заливкой var(--accent) на .active -
     * теперь фон активной вкладки полностью на совести скользящей
     * капсулы (.chat-folder-tabs-indicator) позади кнопок, кнопка
     * сама остаётся прозрачной, чтобы капсулу было видно сквозь неё.
     */
    background: transparent;
    color: var(--text);
    /* v60.15: чуть увеличен шрифт вкладок, см. сверку со скриншотами
       Telegram в docs/04-visual-sizing-telegram-comparison.md. */
    font-size: 14px;
    line-height: 1.3;
    cursor: pointer;
    transition: border-color 140ms ease, color 140ms ease, transform 140ms ease;
}

.chat-folder-tab.active {
    border-color: transparent;
    color: var(--on-accent);
}

.chat-folder-tab:active {
    background: rgba(var(--ink-tint-rgb), 0.1);
    /* v60.21: та же лёгкая анимация нажатия, что и у остальных
       кнопок-таблеток в приложении (см. .push-action-button и т.п.). */
    transform: scale(0.96);
}

.chat-folder-tab.active:active {
    /* v60.28: раньше здесь var(--accent) "перекрашивал" уже залитую
       кнопку заново - теперь фон и так даёт капсула позади, здесь
       достаточно едва заметного затемнения поверх неё. */
    background: rgba(0, 0, 0, 0.08);
}

.chat-list {
    /*
     * v58.14: высота считается через CSS-переменную вместо
     * жёсткого числа - при скрытии/появлении строки поиска
     * список плавно занимает освободившееся место (см. JS,
     * который меняет --chat-list-header-offset).
     *
     * v58.46: дефолт (было 166px) = высота шапки+поиска (развёрнут) +
     * 44px высоты #chatFolderTabs (см. CHAT_FOLDER_TABS_HEIGHT в
     * app.js) - #chatFolderTabs не скрывается при скролле, поэтому
     * его высота всегда часть оффсета, в обоих состояниях поиска.
     * v60.23: 166px -> 158px вслед за уменьшением .sidebar-header
     * (см. CHAT_SEARCH_EXPANDED_OFFSET/CHAT_SEARCH_COLLAPSED_OFFSET
     * в app.js - те же -8px).
     *
     * v58.48: добавлено литеральное вычитание 58px +
     * safe-area-inset-bottom под новый #bottomTabBar снизу - высота
     * таб-бара постоянна во всех состояниях скролла/поиска, поэтому
     * добавлена прямо в CSS calc(), а НЕ через JS-переменную
     * --chat-list-header-offset (тот механизм отвечает только за
     * динамическую часть оффсета сверху и остался нетронутым).
     */
    height: calc(
        100svh -
        var(--chat-list-header-offset, 158px) -
        env(safe-area-inset-top) -
        58px -
        env(safe-area-inset-bottom)
    );
    overflow-y: auto;
    overscroll-behavior: contain;
    background: var(--panel);
    transition: height 260ms ease;
}

/*
 * v58.48: экран "События" (#eventsList) - список упоминаний
 * пользователя (GET /api/mentions, task #92). В отличие от
 * .chat-list, высота считается НЕЗАВИСИМО от JS-переменной
 * --chat-list-header-offset - когда открыта вкладка "События",
 * #chatSearchBar и #chatFolderTabs скрыты (см. setActiveTab() в
 * app.js), над списком остаётся только 72px шапки (.sidebar-header
 * min-height). Та же логика 58px + safe-area-inset-bottom снизу под
 * #bottomTabBar, что и у .chat-list выше.
 */
.events-list {
    height: calc(
        100svh -
        72px -
        env(safe-area-inset-top) -
        58px -
        env(safe-area-inset-bottom)
    );
    overflow-y: auto;
    overscroll-behavior: contain;
    background: var(--panel);
}

/*
 * v58.48: нижний таб-бар (Чаты/События/Настройки), Telegram-style -
 * занимает бывшую серую safe-area-полосу внизу списка чатов (см.
 * project notes/скриншот пользователя). #settingsButton (шестерёнка)
 * переехал сюда из шапки - тот же id, тот же обработчик клика
 * openSettingsScreen в app.js, изменилось только место в DOM и
 * стили.
 *
 * v58.49 (ПРАВКА по жалобе пользователя со скриншотом - "я тебя
 * спрашивал за серую область, а ты эти кнопки поместил НАД серой
 * областью"): первая версия (v58.48) держала таб-бар в обычном
 * потоке И считала env(safe-area-inset-bottom) ДВАЖДЫ - один раз в
 * height (calc(58px + inset)), второй раз в padding-bottom (inset) -
 * итоговая высота блока получалась 58px + 2×inset. Кнопки
 * (justify-content:center внутри .bottom-tab-bar-row) визуально
 * центрировались в этой раздутой высоте и оказывались НАД настоящим
 * серым индикатором Home, а под ними оставалась лишняя пустая
 * серая полоса ровно в один inset высотой - то, что видно на
 * скриншоте пользователя.
 *
 * Исправление - два слоя, как в нативных iOS-таб-барах:
 * 1) #bottomTabBar (эта обёртка) - position:absolute, bottom:0
 *    внутри position:relative .sidebar (см. .sidebar выше). Лежит
 *    ПОВЕРХ нижней части списка (который уже оставляет для неё
 *    58px + inset пустого места снизу через .chat-list/.events-list
 *    height calc() - не менялось). padding-bottom: env(...) здесь
 *    ЕДИНСТВЕННОЕ место, где safe-area-inset-bottom учитывается -
 *    чистый отступ ПОД рядом кнопок, ровно на высоту серой полосы,
 *    фона того же цвета (var(--panel)), поэтому визуально сливается
 *    с ней, а не добавляет вторую такую же полосу.
 * 2) .bottom-tab-bar-row (новый внутренний div в index.html,
 *    обёртывает 3 кнопки) - фиксированные 58px, без каких-либо
 *    safe-area отступов. Именно здесь центрируются иконки/подписи.
 *
 * v58.51 (v58.49 и v58.50 подтверждённо НЕ дали никакого визуального
 * эффекта на реальном iPhone даже после полного force-quit из App
 * Switcher - см. project notes). Обе правки меняли только ФОРМУЛУ
 * вычисления отступа/высоты, но не сам факт, что live env(...) в
 * CSS может быть некорректен именно в момент первой отрисовки
 * standalone PWA после холодного запуска (задокументированный
 * WebKit-баг синхронизации, та же природа, что и у известной
 * проблемы с dvh на cold start - см. research notes). Если
 * env(safe-area-inset-bottom) в этот момент возвращает 0, то
 * ПОЛНОСТЬЮ ЛЮБАЯ формула на его основе даёт 0 - именно поэтому обе
 * прошлые правки не могли дать эффекта: баг не в формуле, а в
 * исходном значении.
 *
 * Фикс: JS отдельно замеряет реальный safe-area-inset-bottom через
 * скрытый probe-элемент (measureSafeAreaInsetBottom/
 * scheduleSafeAreaRecheck в app.js) и перезаписывает
 * --measured-safe-area-bottom при load/resize/orientationchange/
 * pageshow/visibilitychange/focus - то есть именно в моменты, когда
 * WebKit гарантированно успел досчитать настоящий safe-area. max()
 * ниже берёт больший из двух источников: если live env() уже
 * корректен - его же значение и останется (измеренное JS будет
 * таким же); если env() застрял на 0 - используется JS-замер.
 */
/*
 * v58.53: диагностика v58.52 (реальные цифры с устройства
 * пользователя) подтвердила, что геометрия здесь УЖЕ корректна -
 * #bottomTabBar доходит ровно до физического низа экрана (bottom
 * совпадает с innerHeight), safe-area учтён один раз. Серая полоса,
 * которую видит пользователь - это зона жеста Home, которую iOS
 * резервирует принудительно в standalone PWA и не даёт убрать через
 * CSS (см. project notes/research). background: var(--panel) ниже
 * уже красит эту зону в тот же цвет, что и остальной таб-бар - если
 * зона всё равно визуально отличается, это, по всей видимости,
 * собственный полупрозрачный "scrim" iOS для читаемости индикатора
 * поверх контента, а не что-то, чем управляет эта страница.
 */
.bottom-tab-bar {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    padding-bottom: max(
        env(safe-area-inset-bottom, 0px),
        var(--measured-safe-area-bottom, 0px)
    );
    border-top: 1px solid var(--border);
    background: var(--panel);
}

.bottom-tab-bar-row {
    display: flex;
    align-items: stretch;
    height: 58px;
    /*
     * v58.53 (по прямой просьбе пользователя после того, как
     * диагностика v58.52 подтвердила: сама зона home indicator
     * (34px) - это зарезервированная iOS-зона жеста, её нельзя
     * убрать никаким CSS, но можно визуально максимально с ней
     * слиться): фон явно продублирован здесь же (не только на
     * родителе #bottomTabBar) - на случай, если на каких-то
     * устройствах фон не наследуется визуально из-за прозрачных
     * промежуточных слоёв рендеринга WebKit.
     */
    background: var(--panel);
}

.bottom-tab-button {
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    /*
     * v58.54 (v58.53 использовала padding-bottom:4px - пользователь
     * прислал скриншот и подтвердил, что сдвиг НЕЗАМЕТЕН: контент
     * кнопки - иконка(24px)+gap(2px)+подпись(~13px) = ~39px - внутри
     * строки высотой 58px, значит центрирование давало отступ ~9.5px
     * сверху И снизу, а padding-bottom:4px сдвигал контент вниз всего
     * на ~5.5px - физически незаметная разница на экране телефона.
     * Максимальный сдвиг вниз при этой высоте строки - это
     * padding-bottom:0, тогда ВЕСЬ свободный запас (58-39=19px)
     * уходит наверх, а подпись прижимается вплотную к нижней границе
     * строки - то есть к самой границе с зоной жеста Home.
     */
    justify-content: flex-end;
    padding-bottom: 0;
    gap: 2px;
    border: none;
    background: transparent;
    color: var(--muted);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.bottom-tab-button:active {
    background: rgba(var(--ink-tint-rgb), 0.1);
}

.bottom-tab-button.active {
    color: var(--accent);
}

.bottom-tab-icon {
    width: 24px;
    height: 24px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.bottom-tab-label {
    font-size: 11px;
    line-height: 1;
}

.loading-card {
    padding: 28px 20px;
    color: var(--muted);
    text-align: center;
}

.chat-list-section-heading {
    padding: 14px 14px 6px;
    color: var(--muted);
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.chat-item {
    display: grid;
    /* v60.15: колонка аватара 54px -> 62px вслед за .avatar 50px -> 58px. */
    grid-template-columns: 62px minmax(0, 1fr) auto;
    gap: 12px;
    align-items: center;
    width: 100%;
    /* v60.15: 12px -> 14px - строки списка были заметно компактнее Telegram. */
    padding: 14px 14px;
    border: 0;
    border-bottom: 1px solid var(--border);
    background: transparent;
    color: inherit;
    text-align: left;
    cursor: pointer;
    /* v60.21: раньше строка чата вообще не давала отклика на нажатие
       (только :hover, который не работает на тач-экранах, и .active -
       это JS-состояние "открытый чат", а не реакция на тап). Теперь
       есть плавный переход фона под палец, как в Telegram. */
    transition: background 120ms ease;
}

.chat-item:hover,
.chat-item.active {
    background: var(--panel-alt);
}

.chat-item:active {
    /* v60.21: тот же нейтральный тон, что у .settings-row-button:active -
       едва заметная подсветка "тап принят", без спора со значением
       .chat-item.active (текущий открытый чат). */
    background: rgba(var(--ink-tint-rgb), 0.06);
}

.chat-main {
    min-width: 0;
}

.chat-title-row,
.chat-preview-row {
    display: flex;
    align-items: center;
    min-width: 0;
}

.chat-title-row {
    justify-content: space-between;
    gap: 8px;
}

.chat-title {
    overflow: hidden;
    /* v60.15: 16px -> 17px, см. сверку со скриншотами Telegram. */
    font-size: 17px;
    font-weight: 650;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*
 * v58.39 (поиск как в Telegram): подсветка найденного фрагмента в
 * названии чата/имени сотрудника (см. renderHighlightedText в app.js).
 * <mark> - без фоновой заливки (это не HTML-текстовый маркер), только
 * акцентный цвет + чуть плотнее начертание, как в реальном Telegram -
 * жёлтый/оранжевый фон браузерного <mark> по умолчанию здесь неуместен
 * и на тёмной теме нечитаем, поэтому background явно сбрасывается.
 */
.chat-title mark.search-match {
    background: transparent;
    color: var(--accent);
    font-weight: 750;
}

.chat-time {
    flex: 0 0 auto;
    color: var(--muted);
    font-size: 12px;
}

.chat-preview-row {
    gap: 8px;
    margin-top: 5px;
}

.chat-preview {
    flex: 1;
    overflow: hidden;
    color: var(--muted);
    /* v60.15: 14px -> 15px, см. сверку со скриншотами Telegram. */
    font-size: 15px;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.unread-badge {
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    min-width: 22px;
    height: 22px;
    padding: 0 6px;
    border-radius: 999px;
    background: var(--accent);
    color: var(--on-accent);
    font-size: 11px;
    font-weight: 700;
}

.avatar {
    position: relative;
    display: grid;
    flex: 0 0 auto;
    place-items: center;

    /*
     * v58.28: жалоба пользователя - фото сотрудника в поиске
     * (и после открытия диалога) растягивалось овалом и вылезало
     * за пределы кружка. width/height:50px и раньше были заданы,
     * но ничего не удерживало бокс квадратным, если он всё же
     * оказывался сплющен (grid/flex-контекст меняется от страницы
     * к странице - .chat-item, .conversation-header, .message-row
     * и т.д.). aspect-ratio + min/max явно фиксируют квадрат
     * независимо от контекста - этого достаточно: <img> с
     * border-radius:inherit и так обрезается по кругу сам по себе
     * (border-radius клипует собственное содержимое элемента, без
     * overflow:hidden на родителе).
     *
     * v58.29: убран overflow:hidden, добавленный в v58.28 - он
     * заодно обрезал .presence-dot (индикатор статуса - зелёный/
     * жёлтый/серый кружок), который является ДОЧЕРНИМ элементом
     * .avatar и намеренно выступает за пределы вписанной окружности
     * (right:1px/bottom:1px в углу квадрата). Круглый overflow-клип
     * срезал именно этот выступающий кусок точки статуса. Квадратность
     * бокса и так гарантирована aspect-ratio+min/max ниже, поэтому
     * overflow:hidden был не нужен и только вредил.
     *
     * v58.30: НАСТОЯЩАЯ причина овала (подтверждена вживую через
     * DevTools/Claude in Chrome на проде, а не догадкой): у .avatar
     * НЕТ явного grid-template-columns/rows - единственный неявный
     * grid-трек размерается по алгоритму 'auto'. Поскольку
     * place-items:center выставляет align-items:center (а не
     * дефолтный stretch), дочерний <img> НЕ растягивается на
     * весь трек - он размерается по своему auto/max-content.
     * Ширина при этом всё равно получает верное значение (50px,
     * от колонки), а высота вместо height:100% откатывается к
     * intrinsic aspect-ratio самой фотографии (naturalWidth/
     * naturalHeight) - т.е. bounding rect картинки становится,
     * например, 50x66.7px для портретного фото вместо 50x50.
     * border-radius:inherit на этом НЕквадратном боксе <img>
     * и рисует эллипс/овал - независимо от overflow на родителе
     * (это и объясняет, почему v58.28/v58.29 не помогли: они
     * трогали только overflow родителя, а не реальный размер img).
     * Фикс: явный grid-template-columns/rows с тем же px, что и
     * width/height, - это делает грид-трек ОПРЕДЕЛЁННОГО (не auto)
     * размера, и height:100% на <img> корректно резолвится в 50px
     * независимо от place-items:center.
     *
     * v60.15: 50px -> 58px - сверка со скриншотами Telegram
     * (список чатов) показала, что аватар в списке у нас заметно
     * мельче относительно строки, см. docs/04-visual-sizing-
     * telegram-comparison.md.
     */
    aspect-ratio: 1;
    width: 58px;
    min-width: 58px;
    max-width: 58px;
    height: 58px;
    min-height: 58px;
    max-height: 58px;
    grid-template-columns: 58px;
    grid-template-rows: 58px;
    border-radius: 50%;
    background: var(--muted-2);
    color: var(--on-accent);
    font-weight: 700;
    letter-spacing: 0.2px;
}

.avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: inherit;
}

.presence-dot {
    position: absolute;
    right: 1px;
    bottom: 1px;
    z-index: 2;
    width: 13px;
    height: 13px;
    border: 2px solid var(--panel);
    border-radius: 50%;
    background: var(--muted-3);
    box-sizing: border-box;
    pointer-events: none;
}

.avatar-small .presence-dot {
    right: 0;
    bottom: 0;
    width: 12px;
    height: 12px;
}

.presence-online {
    background: var(--success);
}

.presence-dnd {
    background: var(--danger);
}

.presence-away {
    background: var(--warning);
}

.presence-offline {
    background: var(--muted-3);
}

.conversation-status[data-presence='online'] {
    color: var(--success-strong);
}

.conversation-status[data-presence='dnd'] {
    color: var(--danger);
}

.conversation-status[data-presence='away'] {
    color: var(--warning-strong);
}

.avatar-small {
    /*
     * v58.28: см. .avatar выше - тот же защитный фикс от овала.
     * v58.30: настоящая причина и фикс (grid-template-columns/rows)
     * тоже см. в .avatar выше - здесь тот же приём с 42px вместо 50px.
     */
    aspect-ratio: 1;
    width: 42px;
    min-width: 42px;
    max-width: 42px;
    height: 42px;
    min-height: 42px;
    max-height: 42px;
    grid-template-columns: 42px;
    grid-template-rows: 42px;
}

.avatar-large {
    /*
     * v58.43 (профиль в экране настроек): тот же защитный приём, что
     * и у .avatar-small выше (aspect-ratio + явные grid-template-
     * columns/rows) - см. подробный разбор бага с овалом у .avatar,
     * здесь просто больший размер, 88px вместо 50/42px.
     */
    aspect-ratio: 1;
    width: 88px;
    min-width: 88px;
    max-width: 88px;
    height: 88px;
    min-height: 88px;
    max-height: 88px;
    grid-template-columns: 88px;
    grid-template-rows: 88px;
    font-size: 30px;
}

.conversation {
    position: relative;
    display: flex;
    min-width: 0;
    height: 100svh;
    flex-direction: column;
    background:
        radial-gradient(
            circle at 10% 20%,
            rgba(var(--accent-rgb), 0.06),
            transparent 24%
        ),
        radial-gradient(
            circle at 90% 80%,
            rgba(var(--accent-2-rgb), 0.07),
            transparent 30%
        ),
        var(--bg);
}

.conversation-header {
    z-index: 2;
    flex: 0 0 auto;
    gap: 12px;
    /*
     * v60.24: 10px -> 7px (сверху/снизу). Хотфикс регресса v60.23:
     * там min-height общей .sidebar-header/.conversation-header был
     * снижен 72px -> 64px, и .sidebar-header получил такую же правку
     * своего padding (12px -> 9px), а .conversation-header - НЕТ,
     * остался 10/18/10. При box-sizing:border-box это давало ровно
     * 64 - 20 = 44px доступного места - точно под размер кнопки
     * "назад" (44x44), она вплотную упиралась в верх и низ шапки без
     * зазора ("кнопка назад сжалась"). Раньше (72px, тот же padding)
     * запас был 8px (4 сверху/снизу). Теперь 64 - 14 = 50px доступно,
     * запас 6px (3 сверху/снизу) - тот же приём, что и у
     * .sidebar-header, просто был пропущен для этого селектора.
     */
    padding:
        calc(7px + env(safe-area-inset-top))
        18px
        7px;
    /*
     * v58.35: тонкая граница вместо box-shadow на общем --surface-rgb -
     * теперь это единственный визуальный разделитель шапки от контента
     * (в оригинале шапка отделялась контрастной тёмной заливкой, теперь
     * шапка светлая и нуждается в явной границе - см. --header-border).
     */
    border-bottom: 1px solid var(--header-border);
}

.back-button {
    /* v60.21: раньше это была узкая область 34x42 с гигантским
       глифом-стрелкой без круглого фона и без tap-feedback - легко
       было промахнуться мимо самой стрелки. Теперь это круглая
       кнопка того же размера, что и .icon-button (44x44), стрелка
       отцентрирована внутри неё flex-ом, тач-зона - весь круг. */
    display: none;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border-radius: 50%;
    /*
     * v60.32: positioning-контекст для ::before-капсулы (см. ниже) -
     * без этого inset:0 у капсулы анкорился бы к .conversation-header,
     * а не к самой кнопке.
     */
    position: relative;
    /*
     * v60.29: font-size/line-height здесь раньше центрировали
     * текстовый глиф "‹" - убраны за ненадобностью, стрелка теперь
     * SVG (.back-button-icon ниже), её размер задаётся отдельно и не
     * зависит от типографики кнопки.
     */
    /*
     * v60.25: хотфикс "кнопка назад сжимается на длинных названиях
     * чата". .back-button - прямой flex-ребёнок .conversation-header
     * без собственного flex-shrink (в отличие от .avatar и
     * .conversation-overflow-wrap, у которых уже flex:0 0 auto).
     * .conversation-title-wrap (flex:1 1 auto, min-width:0) сжимается
     * вплоть до 0 при длинном названии через ellipsis - но если
     * этого всё равно не хватает, алгоритм flexbox отдаёт остаток
     * сжатия единственному оставшемуся гибкому элементу - кнопке
     * "назад", уменьшая её реальный рендер ниже 44x44. На коротких
     * названиях overflow не возникает, поэтому баг был незаметен
     * только там. flex-shrink:0 гарантирует кнопке фиксированный
     * размер независимо от длины названия.
     */
    flex-shrink: 0;
    /*
     * v60.22: v60.21 сделала тач-зону круглой, но фон остался
     * transparent (как у всех .icon-button) - в состоянии покоя
     * кружок был невидим, заметен только при нажатии. Теперь у
     * кнопки видимый фон в покое - тот же язык, что уже используют
     * .push-bell-button/.sidebar-settings-button (лёгкий градиент +
     * тонкая граница + мягкая тень), чтобы все круглые кнопки шапки
     * выглядели единообразно.
     */
    border: 1px solid rgba(var(--border-tint-rgb), 0.24);
    background: linear-gradient(
        180deg,
        rgba(var(--surface-rgb), 0.98),
        rgba(var(--surface-rgb), 0.94)
    );
    box-shadow:
        0 1px 2px rgba(var(--shadow-rgb), 0.06),
        0 6px 18px rgba(var(--shadow-rgb), 0.08);
}

.conversation-title-wrap {
    min-width: 0;
    flex: 1 1 auto;
}

.conversation-title {
    overflow: hidden;
    font-size: 16px;
    font-weight: 700;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.conversation-status {
    margin-top: 2px;
    color: var(--header-text-muted);
    font-size: 12px;
}

.group-notification-button {
    /* v58.74: управление уведомлениями группы перенесено в меню «⋮».
       Элемент скрыт (display:none) - размер ниже правится для
       консистентности на случай будущего включения, визуально сейчас
       ни на что не влияет. */
    display: none !important;
    flex: 0 0 auto;
    place-items: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--header-icon);
    cursor: pointer;
}

.group-notification-button:active {
    background: rgba(var(--ink-tint-rgb), 0.12);
}

.group-notification-button:disabled {
    cursor: wait;
    opacity: 0.55;
}

.group-notification-button svg {
    width: 24px;
    height: 24px;
    fill: none;
    stroke: currentColor;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-width: 1.8;
}

.group-notification-slash {
    opacity: 0;
    stroke-width: 2.2;
    transition: opacity 140ms ease;
}

.group-notification-button.is-muted {
    color: var(--warning-light);
}

.group-notification-button.is-muted .group-notification-slash {
    opacity: 1;
}

/*
 * v58.23: панель режима множественного выбора сообщений.
 * Переиспользует класс .conversation-header (тот же flex-ряд,
 * отступы, фон, безопасная зона сверху) - отличия только в
 * содержимом (счётчик + 3 кнопки действий вместо аватара/
 * заголовка). Показывается вместо #conversationHeader через
 * взаимоисключающий .hidden на обоих (см. updateSelectionUI).
 */
.selection-count {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    font-size: 16px;
    font-weight: 700;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.selection-actions {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    gap: 4px;
}

.selection-action-button:disabled {
    cursor: default;
    opacity: 0.4;
}

.selection-action-button.danger:not(:disabled) {
    color: var(--danger-light);
}

/*
 * Подсветка выбранных сообщений и лёгкое затемнение остальных -
 * только CSS-классы, БЕЗ новых DOM-узлов в каждой строке (не
 * трогает инвариант инкрементального рендера ленты).
 */
.message-area.selection-mode .message-row {
    cursor: pointer;
}

.message-row.message-row-selected .message-bubble {
    box-shadow:
        0 0 0 2px var(--accent),
        0 1px 2px rgba(var(--shadow-rgb), 0.1);
}

.message-area.selection-mode .message-row:not(.message-row-selected) .message-bubble {
    opacity: 0.86;
}

.empty-state {
    display: grid;
    flex: 1;
    place-content: center;
    padding: 30px;
    text-align: center;
}

.empty-state h1 {
    margin: 15px 0 6px;
    font-size: 25px;
}

.empty-state p {
    margin: 0;
    color: var(--muted);
}

.empty-logo {
    display: grid;
    place-items: center;
    width: 88px;
    height: 88px;
    margin: auto;
    border-radius: 28px;
    background: linear-gradient(145deg, var(--accent), var(--accent-2));
    box-shadow: var(--shadow);
    color: var(--on-accent);
    font-size: 29px;
    font-weight: 800;
}

.message-area {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: 5px;
    overflow-y: auto;
    padding: 18px max(18px, 7vw);
    overscroll-behavior: contain;
}

.message-row {
    display: flex;
    width: 100%;
}

.message-row.mine {
    justify-content: flex-end;
}

.message-row.theirs {
    justify-content: flex-start;
}

.message-bubble {
    max-width: min(76%, 640px);
    padding: 8px 11px 6px;
    border-radius: 16px;
    box-shadow: 0 1px 2px rgba(var(--shadow-rgb), 0.1);
    white-space: pre-wrap;
    overflow-wrap: anywhere;

    /*
     * v58.22: у пузыря не было ни user-select, ни touch-callout -
     * поэтому долгое нажатие иногда запускало нативное выделение
     * текста (с лупой и системным Copy/Look Up callout) ОДНОВРЕМЕННО
     * с нашим кастомным меню действий (открывается через 520мс
     * JS-таймер на pointerdown, см. messageLongPressTimer). Само
     * выделение тексту сообщений не нужно - копирование уже есть
     * как пункт меню (copyMessageText делает это программно, без
     * выделения пользователем). Отключаем оба нативных механизма.
     */
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
}

.message-row.mine .message-bubble {
    border-bottom-right-radius: 5px;
    background: var(--outgoing);
}

.message-row.theirs .message-bubble {
    border-bottom-left-radius: 5px;
    background: var(--incoming);
}

.message-sender {
    margin-bottom: 4px;
    color: var(--accent);
    font-size: 12px;
    font-weight: 700;
}

.message-text {
    font-size: 16px;
    line-height: 1.4;
}

.message-text a {
    color: var(--accent);
    text-decoration: underline;
    word-break: break-word;
}

/*
 * v58.25: упоминание пользователя ([USER=ID]Имя[/USER] из
 * Bitrix) - кликабельно, открывает личный чат с этим userId.
 */
.message-mention {
    color: var(--accent);
    font-weight: 650;
    cursor: pointer;
    word-break: break-word;
}

.message-meta {
    margin-top: 4px;
    color: var(--muted);
    font-size: 12px;
    text-align: right;
}

.composer {
    position: relative;
    display: flex;
    flex: 0 0 auto;
    align-items: flex-end;
    gap: 10px;
    padding:
        10px
        max(12px, 5vw)
        calc(10px + env(safe-area-inset-bottom));
    border-top: 1px solid rgba(var(--shadow-rgb), 0.08);
    /* v60.39: убран backdrop-filter - лента сообщений прокручивается
       под композером, блюр backdrop'а пересчитывался на каждом кадре. */
    background: rgba(var(--surface-rgb), 0.98);
}

.composer textarea {
    flex: 1;
    max-height: 132px;
    min-height: 44px;
    resize: none;
    overflow-y: auto;
    padding: 11px 15px;
    border: 1px solid var(--input-border);
    border-radius: 22px;
    outline: none;
    background: var(--input-bg);
    color: var(--text);
    font-size: 16px;
    line-height: 21px;
}

.composer textarea:focus {
    border-color: var(--accent);
}

.send-button {
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    width: 44px;
    height: 44px;
    padding: 0 2px 1px 0;
    border: 0;
    border-radius: 50%;
    background: var(--accent);
    color: var(--on-accent);
    cursor: pointer;
    font-size: 19px;
}

.send-button:active {
    background: var(--accent-hover);
}

.send-button:disabled {
    cursor: default;
    opacity: 0.55;
}

.toast {
    position: fixed;
    z-index: 50;
    right: 18px;
    bottom: calc(18px + env(safe-area-inset-bottom));
    max-width: min(360px, calc(100vw - 36px));
    padding: 12px 15px;
    border-radius: 12px;
    background: var(--toast-bg);
    box-shadow: var(--shadow);
    color: var(--on-accent);
    font-size: 13px;
}

@media (max-width: 780px) {
    .app-shell {
        display: block;
    }

    .sidebar,
    .conversation {
        width: 100%;
        height: 100svh;
    }

    /*
     * v60.31: слайд-переход список чатов <-> открытый диалог.
     *
     * История: v58.23 добавил ИНТЕРАКТИВНЫЙ touch-драг (палец таскает
     * экран вручную) - v58.24 его убрал, т.к. "работал нестабильно на
     * реальном устройстве", вернувшись к простому display:none/flex
     * без анимации вовсе. Здесь НЕ повторяем v58.23 - никаких новых
     * touch/pointer-обработчиков не добавлено, слайд всего лишь
     * анимирует ТУ ЖЕ переменную, что и раньше решала показ/скрытие -
     * body.chat-open (её переключают существующие клики по чату и
     * кнопке "назад", см. app.js), просто вместо мгновенного
     * display:none/flex теперь CSS transition на transform.
     *
     * Оба экрана держим в DOM одновременно (position:fixed, а не
     * display:none) и просто уводим неактивный за пределы экрана по X -
     * это то, что делает Telegram/нативный UINavigationController.
     * pointer-events:none на уехавшем экране - чтобы он не перехватывал
     * тапы, пока визуально скрыт за краем.
     */
    /*
     * v60.76 / уточнено v60.77: «мягче» открытие диалога и «назад ко
     * всем чатам». Было (v60.31): 320ms, оба экрана едут на всю ширину —
     * визуально «две плиты» меняются местами, старт резковат.
     * Стало — 1:1 повтор нативного iOS UINavigationController:
     *  - наезжающий экран (диалог при открытии / список при «назад»)
     *    полноценно въезжает сбоку с мягкой тенью на ведущем крае
     *    (в покое тень прозрачна — не мажет край соседнего экрана);
     *  - уходящий экран отъезжает вглубь (параллакс) и накрывается
     *    затемняющей шторкой .sidebar::after — именно это даёт
     *    «нативное» ощущение, а не полупрозрачность самого экрана;
     *  - v60.78: параметры усилены, чтобы переход было ВИДНО
     *    (пользователь: «на глаз не заметно, как будто так и было») —
     *    400ms, уезд списка -45%, шторка 0.55, тень 0.24.
     *    Если станет слишком — вернуть к 340ms / -28% / 0.34 / 0.18.
     * Переключает всё та же body.chat-open (клики по чату и кнопке
     * «назад» в app.js) — НИКАКОГО нового JS/обработчиков.
     */
    .sidebar,
    .conversation {
        position: fixed;
        inset: 0;
        transition:
            transform 400ms cubic-bezier(0.32, 0.72, 0, 1),
            box-shadow 400ms cubic-bezier(0.32, 0.72, 0, 1);
        will-change: transform;
    }

    /*
     * Затемняющая шторка на списке, пока открыт диалог (и при
     * открытии, и при «назад» — список всегда нижний экран).
     */
    .sidebar::after {
        content: '';
        position: absolute;
        inset: 0;
        z-index: 60;
        background: #000;
        opacity: 0;
        pointer-events: none;
        transition: opacity 400ms cubic-bezier(0.32, 0.72, 0, 1);
    }

    .sidebar {
        transform: translateX(0);
        z-index: 1;
    }

    .conversation {
        display: flex;
        transform: translateX(100%);
        z-index: 2;
        pointer-events: none;
        box-shadow: -18px 0 46px rgba(var(--shadow-rgb), 0);
    }

    /* список уезжает частично вглубь и затемняется — параллакс */
    body.chat-open .sidebar {
        transform: translateX(-45%);
        pointer-events: none;
    }

    body.chat-open .sidebar::after {
        opacity: 0.55;
    }

    body.chat-open .conversation {
        transform: translateX(0);
        pointer-events: auto;
        box-shadow: -18px 0 46px rgba(var(--shadow-rgb), 0.24);
    }

    .back-button {
        /* v60.21: flex, а не block - .back-button теперь центрирует
           стрелку через align-items/justify-content (см. правку выше). */
        display: flex;
    }

    .message-area {
        padding: 14px 10px;
    }

    .message-bubble {
        max-width: 86%;
    }

    .composer {
        padding-right: 10px;
        padding-left: 10px;
    }
}

/* v60.31: как и остальные анимации в проекте (см. другие
   @media(prefers-reduced-motion:reduce) блоки выше по файлу) - слайд
   список<->диалог тоже уважает системную настройку "уменьшить
   движение". */
@media (max-width: 780px) and (prefers-reduced-motion: reduce) {
    .sidebar,
    .conversation,
    .sidebar::after {
        transition-duration: 1ms !important;
    }
}
/*
 * Кнопка скрытия клавиатуры показывается только
 * на мобильном устройстве и только пока поле в фокусе.
 */
.keyboard-button {
    display: none;
    flex: 0 0 auto;
    place-items: center;
    width: 38px;
    height: 44px;
    padding: 0 0 7px;
    border: 0;
    border-radius: 19px;
    background: transparent;
    color: var(--muted);
    cursor: pointer;
    font-size: 28px;
    font-weight: 500;
    line-height: 1;
    touch-action: manipulation;
}

.keyboard-button:active {
    background: rgba(var(--shadow-rgb), 0.08);
}

@media (max-width: 780px) {
    .composer.keyboard-open .keyboard-button {
        display: grid;
    }
}

/*
 * iChat mobile viewport v10.
 *
 * v58.50 (жалоба пользователя со скриншотом на реальном iPhone,
 * iChat с экрана "Домой" - "под таб-баром всё ещё лишний светлый
 * отступ", после того как v58.49 убрала задвоение
 * safe-area-inset-bottom внутри .bottom-tab-bar): дефолт этой
 * переменной был 100svh - известный WebKit-квирк в standalone PWA
 * заключается в том, что svh (small viewport height) может быть
 * МЕНЬШЕ реальной высоты экрана даже когда Safari вообще не
 * показывает свой UI (расчёт идёт от гипотетического
 * "minimal-chrome" состояния). .app-shell (position:fixed на
 * мобильных, см. ниже) использовал height: var(--ichat-viewport-height)
 * = 100svh - и получался на несколько пикселей короче реального
 * экрана, оставляя под собой полосу фона <body>, визуально видную
 * именно там, где теперь стоит #bottomTabBar (раньше эта нехватка
 * скрывалась - под чат-листом не было элемента, специально
 * прижатого к нижнему краю).
 *
 * Дефолт заменён на 100% - для position:fixed процент высоты
 * резолвится от initial containing block (реальный viewport), без
 * svh-специфичного поведения. html/body в этом же медиа-запросе уже
 * используют проверенный height:100% - теперь .app-shell считает
 * так же. JS-override при видимой клавиатуре (явное px-значение
 * через --ichat-viewport-height) не тронут - см. applyMobileViewport
 * в app.js.
 */
:root {
    --ichat-viewport-height: 100%;
    --ichat-viewport-top: 0px;
}

@media (max-width: 780px) {
    html,
    body {
        width: 100%;
        height: 100%;
        overflow: hidden;
        overscroll-behavior: none;
    }

    .app-shell {
        position: fixed;
        top: var(--ichat-viewport-top);
        right: 0;
        bottom: auto;
        left: 0;

        width: 100%;
        height: var(--ichat-viewport-height);
        max-height: var(--ichat-viewport-height);
        min-height: 0;

        overflow: hidden;
    }

    .sidebar,
    .conversation {
        width: 100%;
        height: 100%;
        min-height: 0;
        max-height: 100%;
    }

    .conversation {
        overflow: hidden;
    }

    .conversation-header {
        position: relative;
        z-index: 20;
        flex: 0 0 auto;
    }

    .message-area {
        position: relative;
        z-index: 1;

        flex: 1 1 auto;
        min-height: 0;

        overflow-x: hidden;
        overflow-y: auto;

        overscroll-behavior-y: contain;
        -webkit-overflow-scrolling: touch;
        touch-action: pan-y;
    }

    .composer {
        position: relative;
        z-index: 30;
        flex: 0 0 auto;
    }

    /*
     * VisualViewport уже заканчивается прямо
     * над клавиатурой. Повторный safe-area снизу
     * создавал бы лишнюю белую полосу.
     */
    body.keyboard-visible .composer {
        padding-bottom: 8px;
    }

    /*
     * Старую кнопку оставляем в DOM, чтобы не ломать
     * существующий JS, но визуально убираем.
     */
    .keyboard-button {
        display: none !important;
    }
}

/* Message likes v11 */
.message-bubble {
    position: relative;
    touch-action: manipulation;
}

.message-like {
    /*
     * v60.18: раньше у кнопки был собственный верхний отступ - имел
     * смысл, пока она была отдельным блочным элементом ПОСЛЕ
     * .message-meta (лишняя строка снизу). Теперь это первый
     * flex-элемент ВНУТРИ самой .message-meta (см. .message-meta.has-like
     * ниже и app.js) - лайк и время в одной строке, свой отступ сверху
     * этому элементу больше не нужен.
     */
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    /*
     * v60.52 (после v60.51: бейдж 14px "смотрелся хреново"). Вернули
     * нормальный размер (20px), но появление лайка при инкрементальном
     * обновлении по-прежнему НЕ меняет высоту строки - лайк,
     * "прилетевший" после холодного открытия чата, двигал ленту на
     * ~8px. Приём: вертикальные отрицательные margin'ы компенсируют
     * высоту. border-box = 20px, но margin-box = 20 - 3 - 3 = 14px =
     * ровно высота .message-meta без лайка (flex, align-items:center -
     * высота строки считается по margin-box, см. замер headless).
     * Бейдж визуально выступает на 3px вверх (в зазор
     * .message-meta{margin-top:3px}) и на 3px вниз (в padding-bottom
     * пузыря 6px), полностью оставаясь внутри пузыря - на соседние
     * строки не наезжает. Delta высоты строки при появлении лайка = 0.
     * Основной способ лайкнуть - двойной тап по пузырю; бейдж -
     * вторичный контрол (снять/добавить лайк, открыть список).
     */
    min-width: 40px;
    height: 20px;
    margin-top: -3px;
    margin-bottom: -3px;
    padding: 0 9px;
    border: 1px solid rgba(var(--muted-2-rgb), 0.22);
    border-radius: 10px;
    background: rgba(var(--surface-rgb), 0.72);
    color: var(--muted-2);
    cursor: pointer;
    font-size: 12px;
    font-weight: 700;
    line-height: 1;
    touch-action: manipulation;
    -webkit-tap-highlight-color: transparent;
}

/*
 * v60.51: мягкое появление бейджа лайка, когда он добавляется точечным
 * патчем meta (класс ставит patchMessageMetaInPlace). Высоту не меняет
 * (delta строки = 0), только opacity/scale - чтобы приход лайка читался
 * как осмысленная микроанимация, а не глитч.
 */
@keyframes message-like-enter {
    from { opacity: 0; transform: scale(0.7); }
    to { opacity: 1; transform: scale(1); }
}

.message-like.message-like-enter {
    animation: message-like-enter 160ms ease-out both;
}

.message-like.active {
    border-color: rgba(var(--accent-rgb), 0.34);
    background: rgba(var(--accent-rgb), 0.12);
    color: var(--accent);
}

.message-like:active {
    transform: scale(0.94);
}

.message-bubble.like-pending {
    opacity: 0.72;
}

/* Infinite message history v12 */
.message-area.history-loading::before {
    content: 'Загружаем историю…';
    position: sticky;
    z-index: 10;
    top: 4px;
    align-self: center;
    flex: 0 0 auto;
    margin: -4px auto 6px;
    padding: 6px 11px;
    border-radius: 14px;
    background: rgba(var(--surface-rgb), 0.97);
    box-shadow: 0 1px 5px rgba(var(--shadow-rgb), 0.14);
    color: var(--muted);
    font-size: 11px;
    line-height: 1;
    pointer-events: none;
    /* v60.39: убран backdrop-filter (репейнт на каждом кадре прокрутки). */
}

/* Message actions, quotes and editing v13 */
.composer {
    flex-wrap: wrap;
}

.composer-context {
    display: flex;
    flex: 1 0 100%;
    align-items: center;
    min-width: 0;
    gap: 10px;
    padding: 1px 5px 6px;
}

.composer-context-accent {
    align-self: stretch;
    width: 3px;
    min-height: 37px;
    border-radius: 3px;
    background: var(--accent);
}

.composer-context-content {
    min-width: 0;
    flex: 1;
}

.composer-context-mode {
    overflow: hidden;
    color: var(--accent);
    font-size: 12px;
    font-weight: 750;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.composer-context-text {
    overflow: hidden;
    margin-top: 2px;
    color: var(--muted);
    font-size: 12px;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.composer-context-close {
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    /* v60.15: 32px -> 44px - единый iOS tap-target-минимум для всех
       круглых icon-button, бар цитаты/редактирования станет чуть
       выше - ожидаемый побочный эффект. */
    width: 44px;
    height: 44px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--muted);
    font-size: 25px;
    line-height: 1;
    touch-action: manipulation;
}

.composer-context-close:active {
    background: rgba(var(--shadow-rgb), 0.08);
}

.composer.editing-mode .composer-context-accent {
    background: var(--warning);
}

.composer.editing-mode .composer-context-mode {
    color: var(--warning-strong);
}

/*
 * v60.27: плашка закреплённого сообщения под шапкой чата. Свой
 * локальный пин iChat (не Bitrix - в Bitrix24 REST API нет метода
 * для закрепления отдельного сообщения внутри чата, только
 * im.recent.pin для всего диалога целиком - см. docs/06). Структура
 * и токены намеренно повторяют .composer-context (акцентная полоса +
 * контент + кнопка закрытия) - тот же визуальный язык, что уже
 * используется для реплая/редактирования, только это простой
 * flex:0 0 auto ребёнок .conversation (flex-column), а не часть
 * композера - сидит в потоке между шапкой и лентой сообщений/панелью
 * поиска, ничего не перекрывая.
 */
.pinned-message-banner {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    gap: 10px;
    padding: 8px 18px;
    background: var(--panel);
    border-bottom: 1px solid var(--header-border);
}

.pinned-message-accent {
    align-self: stretch;
    width: 3px;
    min-height: 32px;
    border-radius: 3px;
    background: var(--accent);
}

.pinned-message-content {
    overflow: hidden;
    min-width: 0;
    flex: 1;
}

.pinned-message-author {
    overflow: hidden;
    color: var(--accent);
    font-size: 12px;
    font-weight: 750;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.pinned-message-text {
    overflow: hidden;
    margin-top: 2px;
    color: var(--muted);
    font-size: 13px;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.pinned-message-close {
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--muted);
    font-size: 20px;
    line-height: 1;
    touch-action: manipulation;
}

.pinned-message-close:active {
    background: rgba(var(--shadow-rgb), 0.08);
}

/*
 * v58.25: подсказка упоминания при вводе "@" в поле сообщения.
 * Позиционируется абсолютно над композером (сам .composer -
 * position: relative, см. выше), поэтому не задевает высоту
 * ленты/раскладку - только перекрывает часть сообщений сверху,
 * как обычный автокомплит.
 */
.mention-picker {
    position: absolute;
    z-index: 40;
    right: max(12px, 5vw);
    bottom: 100%;
    left: max(12px, 5vw);
    margin-bottom: 6px;
    max-height: 220px;
    overflow-y: auto;
    border: 1px solid rgba(var(--shadow-rgb), 0.08);
    border-radius: 16px;
    background: rgba(var(--surface-rgb), 0.98);
    box-shadow: 0 12px 30px rgba(var(--shadow-rgb), 0.16);
    backdrop-filter: blur(14px);
}

.mention-picker.hidden {
    display: none;
}

.mention-picker-item {
    display: flex;
    width: 100%;
    align-items: center;
    gap: 10px;
    padding: 9px 12px;
    border: 0;
    background: transparent;
    color: var(--text);
    font: inherit;
    text-align: left;
    cursor: pointer;
    touch-action: manipulation;
}

.mention-picker-item:active {
    background: rgba(var(--accent-rgb), 0.08);
}

.mention-picker-name {
    overflow: hidden;
    min-width: 0;
    font-size: 14px;
    font-weight: 650;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.message-row {
    gap: 3px;
    align-items: center;
}

.message-actions-button {
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    width: 30px;
    height: 34px;
    padding: 0 0 7px;
    border: 0;
    border-radius: 17px;
    background: transparent;
    color: var(--muted-2);
    opacity: 0.58;
    cursor: pointer;
    font-size: 22px;
    font-weight: 700;
    line-height: 1;
    touch-action: manipulation;
    -webkit-tap-highlight-color: transparent;
}

.message-actions-button:active {
    background: rgba(var(--shadow-rgb), 0.08);
    opacity: 1;
}

.message-row:hover .message-actions-button {
    opacity: 1;
}

.message-quote {
    margin: 1px 0 7px;
    padding: 6px 8px;
    border-left: 3px solid var(--accent);
    border-radius: 5px 9px 9px 5px;
    background: rgba(var(--accent-rgb), var(--quote-bg-alpha));
}

.message-quote-author {
    overflow: hidden;
    color: var(--accent);
    font-size: 11px;
    font-weight: 750;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.message-quote-text {
    display: -webkit-box;
    overflow: hidden;
    margin-top: 2px;
    color: var(--muted-2);
    font-size: 12px;
    line-height: 1.25;
    -webkit-box-orient: vertical;
    /*
     * v60.26: 3 -> 2. Полировка блока цитаты - "компактность, две
     * строки максимум" (роадмап-пункт 6). Цитата в пузыре сообщения
     * могла растягиваться на 3 строки, что для превью цитируемого
     * текста избыточно - Telegram и большинство мессенджеров
     * ограничивают цитату двумя строками с многоточием.
     */
    -webkit-line-clamp: 2;
}

.message-bubble.message-deleted {
    background: rgba(var(--panel-alt-border-rgb), 0.86);
    box-shadow: none;
    color: var(--muted);
    font-style: italic;
}

.message-bubble.message-deleted .message-text {
    font-size: 13px;
}

.message-menu-backdrop {
    position: fixed;
    z-index: 200;
    inset: 0;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    padding:
        14px
        12px
        calc(14px + env(safe-area-inset-bottom));
    background: rgba(var(--shadow-rgb), 0.34);
    backdrop-filter: blur(3px);
}

.message-menu-sheet {
    width: min(520px, 100%);
    max-height: min(620px, 88vh);
    overflow-y: auto;
    border-radius: 22px;
    background: var(--panel-alt);
    box-shadow:
        0 20px 55px rgba(var(--shadow-rgb), 0.26);
    animation: message-menu-in 160ms ease-out;
}

@keyframes message-menu-in {
    from {
        opacity: 0;
        transform: translateY(18px) scale(0.98);
    }

    to {
        opacity: 1;
        transform: translateY(0) scale(1);
    }
}

.message-menu-handle {
    width: 38px;
    height: 5px;
    margin: 8px auto 6px;
    border-radius: 4px;
    background: var(--muted-3);
}

.message-menu-preview {
    margin: 0 9px 8px;
    padding: 10px 12px;
    border-radius: 14px;
    background: var(--panel);
}

.message-menu-preview-author {
    overflow: hidden;
    color: var(--accent);
    font-size: 12px;
    font-weight: 750;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.message-menu-preview-text {
    display: -webkit-box;
    overflow: hidden;
    margin-top: 3px;
    color: var(--text);
    font-size: 13px;
    line-height: 1.3;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
}

.message-menu-actions {
    overflow: hidden;
    margin: 0 9px;
    border-radius: 15px;
    background: var(--panel);
}

.message-menu-actions button,
.message-menu-cancel {
    display: flex;
    align-items: center;
    width: 100%;
    min-height: 52px;
    gap: 12px;
    padding: 0 16px;
    border: 0;
    border-bottom: 1px solid var(--panel-alt-border);
    background: var(--panel);
    color: var(--text);
    text-align: left;
    font-size: 16px;
    cursor: pointer;
    touch-action: manipulation;
}

.message-menu-actions button:last-child {
    border-bottom: 0;
}

.message-menu-actions button span {
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    width: 24px;
    color: var(--accent);
    font-size: 20px;
}

.message-menu-actions button.danger {
    color: var(--danger);
}

/*
 * v58.19: у элемента может быть выставлен нативный атрибут
 * hidden (messageMenuElements.X.hidden = true в JS), но
 * авторское правило ".message-menu-actions button { display:
 * flex/grid }" выше по origin, чем стандартное UA-правило
 * "[hidden] { display: none }", и полностью его перебивает.
 * Из-за этого "Удалить у всех" (и "Редактировать") оставались
 * видимыми на чужих сообщениях, хотя JS уже помечал их hidden.
 * Явное правило с более высокой специфичностью восстанавливает
 * ожидаемое поведение.
 */
.message-menu-actions button[hidden] {
    display: none;
}

/*
 * v58.20: picker получателя для "Переслать". Переиспользует
 * .message-menu-backdrop/.message-menu-sheet (уже безопасный
 * паттерн скролла - overflow-y:auto прямо на самом элементе,
 * без flex-column+max-height+flex:1-ребёнка, см. баг v58.14/
 * v58.15) и .chat-search-field/.chat-search-input/.chat-item/
 * .chat-list-section-heading из поиска по чатам и сотрудникам.
 */
.forward-picker-search {
    margin: 0 9px 8px;
}

.forward-picker-results {
    overflow: hidden;
    margin: 0 9px 8px;
    border-radius: 15px;
    background: var(--panel);
}

.forward-picker-results .chat-item:last-child {
    border-bottom: 0;
}

/*
 * v58.21, фикс 1: ниже по файлу есть ВТОРОЕ правило
 * ".message-menu-sheet" (визуальный "glass"-рефреш, добавленный
 * позже) с "overflow: hidden;" - оно того же origin/специфичности
 * и идёт ПОЗЖЕ в источнике, поэтому по каскаду побеждает и
 * перебивает "overflow-y: auto;" из первого правила выше.
 * Из-за этого сам лист (.message-menu-sheet) физически не мог
 * прокручиваться - для обычного меню действий (4-6 кнопок) это
 * было незаметно, они всегда помещались в max-height, но для
 * picker'а "Переслать" с длинным списком получателей прокрутка
 * не работала вовсе. Исправлено точечно у самого второго правила
 * (см. .message-menu-sheet ниже) - здесь только пояснение.
 *
 * v58.21, фикс 2: пока пользователь печатает запрос получателя,
 * отфильтрованный список мгновенно становится короче (сотрудники
 * из сети ещё не пришли), а .message-menu-sheet auto-height +
 * прижатие к низу (align-items: flex-end на backdrop) заставляли
 * весь лист визуально "проседать" вниз при каждом сокращении
 * контента. Фиксированная высота только для picker'а пересылки
 * убирает это дрожание, не трогая обычное меню действий (там
 * достаточно места и высота специально по контенту).
 */
#forwardPickerBackdrop .message-menu-sheet {
    height: min(620px, 88vh);
}

.message-menu-actions button.danger span {
    color: var(--danger);
}

.message-menu-actions button:active,
.message-menu-cancel:active {
    background: var(--panel-alt);
}

.message-menu-cancel {
    justify-content: center;
    margin-top: 9px;
    border-bottom: 0;
    border-radius: 15px;
    color: var(--accent);
    font-weight: 700;
}

body.message-menu-open {
    overflow: hidden;
}

@media (max-width: 780px) {
    .message-actions-button {
        width: 27px;
        opacity: 0.72;
    }

    .message-bubble {
        max-width: calc(86% - 24px);
    }

    .message-menu-backdrop {
        padding-right: 8px;
        padding-left: 8px;
    }
}

/* Smooth infinite history v17 */
.message-area {
    overflow-anchor: none;
    scroll-behavior: auto;
}

/*
 * v60.48: при открытии чата лента строится и прижимается к низу
 * СРАЗУ, но картинки/аватары догружаются после и меняют высоту
 * строк - код каждый раз доскроливает вниз, и это видно как рывки
 * ("дёргает при открытии, особенно в чатах с фото/группах"). Пока
 * .messages-settling - лента невидима (но в layout, картинки
 * грузятся); scheduleMessagesReveal() снимает класс, когда
 * картинки в видимой части догрузились (или по таймауту ~500мс),
 * сделав финальный доскролл к низу. Так пользователь видит уже
 * собранную ленту у нижнего края, без промежуточных прыжков.
 */
.message-area {
    transition: opacity 110ms ease;
}

.message-area.messages-settling {
    opacity: 0;
}

.message-area.history-loading::before {
    content: 'Загрузка…';
    position: absolute;
    z-index: 40;
    top: 8px;
    left: 50%;
    width: auto;
    height: 26px;
    margin: 0;
    padding: 0 11px;
    align-self: auto;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 13px;
    background: rgba(var(--surface-rgb), 0.94);
    box-shadow:
        0 2px 8px rgba(var(--shadow-rgb), 0.14);
    color: var(--muted);
    font-size: 11px;
    line-height: 1;
    pointer-events: none;
    backdrop-filter: blur(10px);
}

.message-area.history-restoring {
    scroll-behavior: auto !important;
}

/* Visible history loading indicator v18 */
.message-area {
    position: relative;
}

.message-area.history-loading::before {
    content: 'Подгружаем историю…';
    position: absolute;
    z-index: 60;
    top: 9px;
    left: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    width: auto;
    height: 28px;
    margin: 0;
    padding: 0 12px;
    transform: translateX(-50%);
    border-radius: 15px;
    background: rgba(var(--surface-rgb), 0.96);
    box-shadow:
        0 2px 9px rgba(var(--shadow-rgb), 0.16);
    color: var(--muted-2);
    font-size: 11px;
    font-weight: 650;
    line-height: 1;
    pointer-events: none;
    backdrop-filter: blur(10px);
    animation: history-indicator-v18 160ms ease-out;
}

@keyframes history-indicator-v18 {
    from {
        opacity: 0;
        transform:
            translateX(-50%)
            translateY(-4px);
    }

    to {
        opacity: 1;
        transform:
            translateX(-50%)
            translateY(0);
    }
}

/* Floating history indicator v19 */

/* Отключаем старый невидимый псевдоэлемент. */
.message-area.history-loading::before {
    content: none !important;
    display: none !important;
}

.history-indicator {
    position: absolute;
    z-index: 55;
    top: calc(
        80px +
        env(safe-area-inset-top)
    );
    left: 50%;
    display: flex;
    align-items: center;
    gap: 7px;
    min-height: 30px;
    padding: 0 13px;
    transform: translateX(-50%);
    border: 1px solid rgba(var(--border-tint-rgb), 0.24);
    border-radius: 16px;
    /* v60.39: убран backdrop-filter - плашка висит поверх
       прокручиваемой ленты, блюр пересчитывался на каждом кадре. */
    background: rgba(var(--surface-rgb), 0.98);
    box-shadow:
        0 3px 12px rgba(var(--shadow-rgb), 0.16);
    color: var(--muted-2);
    font-size: 11px;
    font-weight: 650;
    line-height: 1;
    white-space: nowrap;
    pointer-events: none;
    animation: history-indicator-v19 160ms ease-out;
}

.history-indicator-spinner {
    width: 12px;
    height: 12px;
    border: 2px solid rgba(var(--muted-2-rgb), 0.24);
    border-top-color: var(--muted-2);
    border-radius: 50%;
    animation:
        history-spinner-v19
        650ms
        linear
        infinite;
}

@keyframes history-spinner-v19 {
    to {
        transform: rotate(360deg);
    }
}

@keyframes history-indicator-v19 {
    from {
        opacity: 0;
        transform:
            translateX(-50%)
            translateY(-5px);
    }

    to {
        opacity: 1;
        transform:
            translateX(-50%)
            translateY(0);
    }
}

/* Swipe left to reply v20 */

.message-row {
    position: relative;
    overflow: visible;
}

.message-bubble::after {
    content: '↩';
    position: absolute;
    z-index: 4;
    top: 50%;
    right: -43px;

    display: grid;
    place-items: center;

    width: 30px;
    height: 30px;

    box-sizing: border-box;
    border: 1px solid rgba(var(--accent-rgb), 0.18);
    border-radius: 50%;

    background: rgba(var(--surface-rgb), 0.94);
    box-shadow:
        0 2px 7px rgba(var(--shadow-rgb), 0.14);

    color: var(--accent);
    font-family:
        -apple-system,
        BlinkMacSystemFont,
        sans-serif;
    font-size: 18px;
    font-weight: 700;
    line-height: 1;

    opacity:
        var(
            --reply-swipe-progress,
            0
        );

    transform:
        translateY(-50%)
        scale(0.92);

    pointer-events: none;

    transition:
        opacity 90ms linear,
        background 110ms ease,
        color 110ms ease,
        transform 110ms ease;
}

.message-row.reply-swipe-active
.message-actions-button {
    opacity: 0;
    pointer-events: none;
}

.message-row.reply-swipe-armed
.message-bubble::after {
    border-color: var(--accent);
    background: var(--accent);
    color: var(--on-accent);

    transform:
        translateY(-50%)
        scale(1.06);
}

.message-row.reply-swipe-resetting
.message-bubble::after {
    opacity:
        var(
            --reply-swipe-progress,
            0
        );
}

@media (max-width: 780px) {
    /*
     * Вертикальный жест остаётся прокруткой,
     * горизонтальный передаётся нашему JS.
     */
    .message-bubble {
        touch-action: pan-y;
        will-change: transform;
    }
}

@media (prefers-reduced-motion: reduce) {
    .message-bubble,
    .message-bubble::after {
        transition-duration: 1ms !important;
    }
}

/* Authentication frontend v23 */

.auth-screen {
    position: fixed;
    z-index: 1000;
    inset: 0;

    display: grid;
    place-items: center;

    min-height: 100%;
    padding:
        calc(28px + env(safe-area-inset-top))
        18px
        calc(28px + env(safe-area-inset-bottom));

    overflow-y: auto;

    background:
        radial-gradient(
            circle at 15% 5%,
            rgba(var(--accent-2-rgb), 0.28),
            transparent 34%
        ),
        radial-gradient(
            circle at 90% 90%,
            rgba(var(--accent-rgb), 0.22),
            transparent 38%
        ),
        linear-gradient(
            /*
             * v58.39: было var(--header) - до этапа 2 (v58.35) --header
             * означал "тёмно-синий" (#0f172a) и как первый стоп градиента
             * подходил случайно совпадая по смыслу. После рёбрендинга шапки
             * --header стал означать "фон шапки" (белый в светлой теме) -
             * этот стоп градиента незаметно сломался (белое пятно вместо
             * тёмно-синего в начале hero-фона экрана входа). Восстановлен
             * буквальным hex - теперь ВСЕ 3 стопа градиента буквальные и
             * theme-invariant, как и было изначально задокументировано.
             */
            155deg,
            #0f172a,
            #172554 58%,
            #0c4a6e
        );

    -webkit-overflow-scrolling: touch;
}

.auth-card {
    width: min(100%, 390px);
    padding: 30px 25px 24px;

    border: 1px solid rgba(var(--surface-rgb), 0.2);
    border-radius: 26px;

    background: rgba(var(--surface-rgb), 0.97);
    box-shadow:
        0 24px 70px rgba(var(--shadow-rgb), 0.36);

    /*
     * v58.39: было var(--header) - тот же баг рёбрендинга шапки, что и
     * у градиента .auth-screen выше (см. комментарий там): --header
     * раньше случайно совпадал с обычным тёмным текстом, а с этапа 2
     * стал означать "фон шапки" (белый) - текст карточки входа стал
     * белым на светлой (в основном) карточке. var(--text) - обычный
     * токен текста, корректно читаем в обеих темах.
     */
    color: var(--text);
    backdrop-filter: blur(18px);
}

.auth-logo {
    display: grid;
    place-items: center;

    width: 72px;
    height: 72px;
    margin: 0 auto 19px;

    border-radius: 23px;

    background:
        linear-gradient(
            145deg,
            var(--accent),
            var(--accent-2)
        );

    box-shadow:
        0 13px 30px rgba(var(--accent-rgb), 0.3);

    color: var(--on-accent);
    font-size: 25px;
    font-weight: 850;
    letter-spacing: -1px;
}

.auth-heading {
    margin-bottom: 23px;
    text-align: center;
}

.auth-heading h1 {
    margin: 0;
    font-size: 25px;
    line-height: 1.2;
}

.auth-heading p {
    margin: 9px 0 0;
    color: var(--muted-2);
    font-size: 14px;
    line-height: 1.45;
}

.auth-form {
    display: grid;
    gap: 16px;
}

.auth-field {
    display: grid;
    gap: 7px;
}

.auth-field > span {
    padding-left: 2px;
    color: var(--muted-strong);
    font-size: 13px;
    font-weight: 700;
}

.auth-field input {
    box-sizing: border-box;
    width: 100%;
    height: 49px;
    padding: 0 14px;

    border: 1px solid var(--input-border);
    border-radius: 14px;
    outline: none;

    background: var(--input-bg);
    /* v58.39: было var(--header) - см. комментарий у .auth-card выше. */
    color: var(--text);

    font-family: inherit;
    font-size: 16px;

    appearance: none;
    -webkit-appearance: none;
}

/*
 * v58.35: намеренное исключение - .auth-screen всегда стилизуется как
 * тёмный градиентный hero со светлой карточкой формы (см. .auth-screen
 * ниже и 3 декоративных hex-стопа градиента), независимо от свет/тёмная
 * тема приложения - как и остальная карточка формы, background здесь
 * НЕ токенизируется намеренно, а не по недосмотру.
 */
.auth-field input:focus {
    border-color: var(--accent);
    background: white;
    box-shadow:
        0 0 0 3px rgba(var(--accent-rgb), 0.12);
}

.auth-field input:disabled {
    opacity: 0.7;
}

/*
 * v60.85: хук детекта автозаполнения. Safari/WebKit применяет
 * :-webkit-autofill в момент подстановки сохранённого логина/пароля
 * (Face ID) -> запускается эта анимация -> JS ловит `animationstart`
 * (event.animationName === 'ichatAutofillHook') и инициирует вход
 * сам, не дожидаясь, пока пользователь закроет клавиатуру и нажмёт
 * «Войти». Кадры пустые - визуально ничего не меняется.
 */
@keyframes ichatAutofillHook {
    from { /* noop */ }
    to { /* noop */ }
}

.auth-field input:-webkit-autofill {
    animation-name: ichatAutofillHook;
    animation-duration: 0.01s;
}

.auth-password-wrap {
    position: relative;
}

.auth-password-wrap input {
    padding-right: 50px;
}

.auth-password-toggle {
    position: absolute;
    top: 50%;
    right: 5px;

    display: grid;
    place-items: center;

    width: 40px;
    height: 40px;
    padding: 0;

    transform: translateY(-50%);

    border: 0;
    border-radius: 11px;

    background: transparent;
    color: var(--muted-2);

    font-size: 17px;
    cursor: pointer;
    touch-action: manipulation;
}

.auth-password-toggle:active {
    background: rgba(var(--shadow-rgb), 0.07);
}

/* v60.61: контейнер виджета Turnstile на форме входа */
.auth-turnstile:empty {
    display: none;
}

.auth-turnstile {
    display: flex;
    justify-content: center;
    min-height: 65px;
}

.auth-error {
    padding: 10px 12px;

    border: 1px solid rgba(var(--danger-rgb), 0.2);
    border-radius: 12px;

    background: rgba(var(--danger-soft-rgb), 0.75);
    color: var(--danger-strong);

    font-size: 13px;
    line-height: 1.35;
}

/* v60.80: нейтральный вид — ожидание Turnstile, не ошибка */
.auth-error.is-hint {
    border-color: rgba(var(--accent-rgb), 0.25);
    background: rgba(var(--accent-rgb), 0.08);
    color: var(--text);
}

.auth-submit {
    position: relative;

    display: grid;
    place-items: center;

    height: 50px;
    margin-top: 2px;
    padding: 0 18px;

    border: 0;
    border-radius: 15px;

    background:
        linear-gradient(
            135deg,
            var(--accent),
            #0284c7
        );

    box-shadow:
        0 9px 22px rgba(var(--accent-rgb), 0.25);

    color: var(--on-accent);
    cursor: pointer;

    font-family: inherit;
    font-size: 15px;
    font-weight: 800;
    touch-action: manipulation;
}

.auth-submit:active {
    transform: translateY(1px);
}

.auth-submit:disabled {
    cursor: default;
    opacity: 0.72;
}

/* v60.82: ждём токен Turnstile — кнопка не отключена (иначе ломается
   неявный submit автозаполнения), только приглушена */
.auth-submit.is-waiting {
    opacity: 0.72;
}

.auth-submit-spinner {
    display: none;

    width: 19px;
    height: 19px;

    border: 2px solid rgba(var(--surface-rgb), 0.35);
    border-top-color: var(--on-accent);
    border-radius: 50%;

    animation:
        auth-spinner-v21
        650ms
        linear
        infinite;
}

.auth-submit.loading .auth-submit-text {
    display: none;
}

.auth-submit.loading .auth-submit-spinner {
    display: block;
}

@keyframes auth-spinner-v21 {
    to {
        transform: rotate(360deg);
    }
}

.auth-security-note {
    margin-top: 18px;
    color: var(--muted-3);
    font-size: 11px;
    line-height: 1.4;
    text-align: center;
}

.sidebar-header-actions {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    gap: 4px;
}

.logout-button {
    font-size: 19px;
}

@media (max-width: 780px) {
    .auth-screen {
        align-items: center;
        /* v58.50: fallback синхронизирован с новым дефолтом :root (100%) */
        height: var(--ichat-viewport-height, 100%);
        min-height: var(--ichat-viewport-height, 100%);
    }

    .auth-card {
        padding: 27px 21px 22px;
        border-radius: 23px;
    }
}

/* Startup gate frontend v22 */

.startup-screen {
    position: fixed;
    inset: 0;
    z-index: 2147483647;

    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 14px;

    width: 100%;
    min-height: 100%;
    min-height: 100svh;

    padding:
        env(safe-area-inset-top)
        env(safe-area-inset-right)
        env(safe-area-inset-bottom)
        env(safe-area-inset-left);

    background: var(--bg);
    color: var(--text);

    opacity: 1;
    visibility: visible;

    transition:
        opacity 180ms ease,
        visibility 180ms ease;
}

.startup-screen-hidden {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}

.startup-spinner {
    width: 38px;
    height: 38px;

    border: 4px solid rgba(var(--accent-rgb), 0.18);
    border-top-color: var(--accent-hover);
    border-radius: 50%;

    animation:
        ichat-startup-spin 750ms linear infinite;
}

.startup-label {
    min-height: 20px;

    font-size: 14px;
    font-weight: 600;
    line-height: 20px;
    letter-spacing: 0.01em;

    color: var(--muted-2);
}

@keyframes ichat-startup-spin {
    to {
        transform: rotate(360deg);
    }
}

@media (prefers-reduced-motion: reduce) {
    .startup-spinner {
        animation-duration: 1500ms;
    }

    .startup-screen {
        transition: none;
    }
}

/* iChat Web Push frontend v23 */

.push-controls {
    position: fixed;
    z-index: 5000;

    top:
        calc(
            12px +
            env(safe-area-inset-top)
        );

    right:
        calc(
            12px +
            env(safe-area-inset-right)
        );
}

.push-bell-button {
    display: flex;
    align-items: center;
    justify-content: center;

    width: 44px;
    height: 44px;

    padding: 0;

    border: 1px solid rgba(var(--shadow-rgb), 0.12);
    border-radius: 50%;

    background: rgba(var(--surface-rgb), 0.96);
    /* v58.39: было var(--header) - см. комментарий у .auth-card выше. */
    color: var(--text);

    box-shadow:
        0 8px 24px rgba(var(--shadow-rgb), 0.16);

    font-size: 20px;
    line-height: 1;

    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.push-bell-button:active {
    transform: scale(0.96);
}

.push-panel {
    position: absolute;
    top: 52px;
    right: 0;

    width: min(320px, calc(100vw - 24px));

    padding: 16px;

    border: 1px solid rgba(var(--shadow-rgb), 0.12);
    border-radius: 16px;

    background: rgba(var(--surface-rgb), 0.98);
    /* v58.39: было var(--header) - см. комментарий у .auth-card выше. */
    color: var(--text);

    box-shadow:
        0 18px 48px rgba(var(--shadow-rgb), 0.22);

    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
}

.push-panel-title {
    margin-bottom: 8px;

    font-size: 16px;
    font-weight: 700;
    line-height: 22px;
}

.push-status {
    min-height: 40px;

    margin-bottom: 14px;

    color: var(--muted-2);

    font-size: 14px;
    line-height: 20px;
}

.push-status[data-status="success"] {
    color: var(--success-strong);
}

.push-status[data-status="warning"] {
    color: var(--warning-strong);
}

.push-status[data-status="error"] {
    color: var(--danger-strong);
}

.push-actions {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.push-action-button {
    min-height: 42px;

    padding: 9px 14px;

    border: 1px solid rgba(var(--shadow-rgb), 0.14);
    border-radius: 10px;

    background: var(--panel);

    /*
     * v58.39: НАЙДЕННЫЙ ПОЛЬЗОВАТЕЛЕМ БАГ - было var(--header). Кнопка
     * "Отправить тест" (без модификатора .push-action-primary/.push-
     * action-danger) наследует именно этот цвет - на белой (var(--panel))
     * подложке белый текст (var(--header) в светлой теме = #ffffff)
     * делал кнопку визуально пустой/белой без видимой надписи. См.
     * комментарий у .auth-card выше - тот же root cause, задевший ещё
     * 5 мест по всему файлу.
     */
    color: var(--text);

    font: inherit;
    font-size: 14px;
    font-weight: 650;

    cursor: pointer;
}

.push-action-primary {
    border-color: var(--accent);
    background: var(--accent);
    /* Та же природа коллизии, что и у .auth-card выше (см. комментарий):
     * var(--panel) - это цвет "поверхности" (белый в светлой теме,
     * тёмный в тёмной), а не "текст на заливке accent". Для текста на
     * сплошной accent-заливке во всём файле используется --on-accent
     * (всегда белый, инвариантен к теме) - см. .send-button,
     * .fab-button и другие. Тут был единственный проброс через --panel,
     * из-за чего в тёмной теме кнопка "Включить пуши" показывала бы
     * тёмный текст на синей заливке вместо белого. */
    color: var(--on-accent);
}

.push-action-danger {
    color: var(--danger-strong);
}

.push-action-button:disabled {
    cursor: wait;
    opacity: 0.55;
}

@media (max-width: 640px) {
    .push-controls {
        top:
            calc(
                8px +
                env(safe-area-inset-top)
            );

        right:
            calc(
                8px +
                env(safe-area-inset-right)
            );
    }

    .push-panel {
        top: 50px;
    }
}

/* Modern notification controls v24 */

.push-controls {
    position: relative !important;

    top: auto !important;
    right: auto !important;
    bottom: auto !important;
    left: auto !important;

    display: inline-flex;
    align-items: center;

    flex: 0 0 auto;

    margin: 0 8px 0 0;
}

.push-controls.hidden {
    display: none !important;
}

.push-bell-button {
    position: relative;

    display: inline-flex;
    align-items: center;
    justify-content: center;

    width: 40px;
    height: 40px;

    padding: 0;

    border:
        1px solid
        rgba(var(--border-tint-rgb), 0.24);

    border-radius: 13px;

    background:
        linear-gradient(
            180deg,
            rgba(var(--surface-rgb), 0.98),
            rgba(var(--surface-rgb), 0.94)
        );

    color: var(--muted-strong);

    box-shadow:
        0 1px 2px rgba(var(--shadow-rgb), 0.06),
        0 6px 18px rgba(var(--shadow-rgb), 0.08);

    cursor: pointer;

    transition:
        transform 140ms ease,
        background 140ms ease,
        border-color 140ms ease,
        box-shadow 140ms ease,
        color 140ms ease;

    -webkit-tap-highlight-color:
        transparent;
}

.push-bell-button:hover {
    border-color:
        rgba(var(--accent-light-rgb), 0.26);

    background: var(--panel);
    color: var(--accent);

    box-shadow:
        0 2px 4px rgba(var(--shadow-rgb), 0.07),
        0 10px 24px rgba(var(--accent-rgb), 0.12);
}

.push-bell-button:active {
    transform: scale(0.95);
}

.push-bell-button[aria-expanded="true"] {
    border-color:
        rgba(var(--accent-rgb), 0.34);

    background:
        rgba(var(--accent-soft-rgb), 0.98);

    color: var(--accent);
}

.push-bell-icon {
    width: 20px;
    height: 20px;

    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.push-bell-dot {
    position: absolute;

    top: 7px;
    right: 7px;

    width: 7px;
    height: 7px;

    border: 2px solid var(--panel);
    border-radius: 50%;

    background: var(--success);

    opacity: 0;
    transform: scale(0.65);

    transition:
        opacity 140ms ease,
        transform 140ms ease;
}

.push-bell-button[data-enabled="true"]
.push-bell-dot {
    opacity: 1;
    transform: scale(1);
}

.push-panel {
    top:
        calc(100% + 10px) !important;

    right: 0 !important;

    width:
        min(
            340px,
            calc(100vw - 24px)
        );

    padding: 18px;

    border:
        1px solid
        rgba(var(--border-tint-rgb), 0.22);

    border-radius: 18px;

    background:
        rgba(var(--surface-rgb), 0.96);

    box-shadow:
        0 24px 60px
        rgba(var(--shadow-rgb), 0.18);

    backdrop-filter: blur(24px);
    -webkit-backdrop-filter: blur(24px);
}

.push-panel-title {
    margin-bottom: 7px;

    font-size: 16px;
    font-weight: 750;
    letter-spacing: -0.01em;
}

.push-status {
    margin-bottom: 16px;

    color: var(--muted-2);

    font-size: 13px;
    line-height: 19px;
}

.push-action-button {
    min-height: 42px;

    border-radius: 12px;

    font-size: 14px;
    font-weight: 700;

    transition:
        transform 120ms ease,
        opacity 120ms ease,
        background 120ms ease;
}

.push-action-button:active {
    transform: scale(0.98);
}

.push-action-primary {
    border-color: var(--accent);

    background:
        linear-gradient(
            135deg,
            var(--accent),
            var(--accent-light)
        );

    box-shadow:
        0 8px 20px
        rgba(var(--accent-rgb), 0.22);
}

@media (max-width: 640px) {
    .push-controls {
        margin-right: 6px;
    }

    .push-bell-button {
        width: 38px;
        height: 38px;

        border-radius: 12px;
    }

    .push-panel {
        right: -48px !important;
    }
}

/* Stable Safari viewport frontend v27 */

/* Compact mobile composer frontend v29 */
@media (max-width: 780px) {
    .composer,
    body.keyboard-visible .composer {
        padding-bottom: 8px !important;
    }
}

/* Push target highlight frontend v30 */
/*
 * v58.9.5: обычный transition вместо @keyframes-анимации.
 * .message-bubble уже транзиционит box-shadow (140ms ease,
 * см. правило выше) - при пересборке строки во время активной
 * подсветки новый узел монтируется СРАЗУ с этим box-shadow,
 * без повторного проигрывания "влёта".
 */
/*
 * v60.79: подсветка пуш-цели. Было `0 0 0 3px … , 0 8px 24px …` —
 * широкий (24px) ореол со СМЕЩЕНИЕМ вниз на 8px, а гас за 140ms
 * (общий transition пузыря). У нижнего края экрана это появление/
 * резкое угасание цветного пятна читалось как «дёрганье», хотя
 * скролл не двигался. Теперь: симметричный компактный ореол без
 * вертикального смещения (пульсирует на месте) и собственный
 * плавный transition ~420ms — угасает мягко.
 */
.message-row.push-target-message .message-bubble {
    box-shadow:
        0 0 0 2px rgba(var(--accent-rgb), 0.28),
        0 0 12px 1px rgba(var(--accent-rgb), 0.10);
    transition: box-shadow 420ms ease-out;
}

/* Push device panel frontend v41 */
.push-devices {
    display: flex;
    flex-direction: column;
    gap: 8px;

    max-height: 240px;

    margin: 0 0 14px;
    padding: 12px 0 0;

    overflow-y: auto;

    border-top:
        1px solid
        rgba(var(--border-tint-rgb), 0.20);
}

.push-devices.hidden {
    display: none !important;
}

.push-devices-title {
    margin: 0 0 2px;

    color: var(--muted-2);

    font-size: 11px;
    font-weight: 800;
    line-height: 16px;
    letter-spacing: 0.055em;
    text-transform: uppercase;
}

.push-device-item {
    display: grid;
    grid-template-columns: 32px minmax(0, 1fr) auto;
    align-items: center;
    gap: 9px;

    min-width: 0;

    padding: 9px 10px;

    border:
        1px solid
        rgba(var(--border-tint-rgb), 0.18);

    border-radius: 12px;

    background:
        rgba(var(--surface-rgb), 0.88);
}

.push-device-current {
    border-color:
        rgba(var(--success-rgb), 0.24);

    background:
        rgba(var(--success-soft-rgb), 0.78);
}

.push-device-icon {
    display: flex;
    align-items: center;
    justify-content: center;

    width: 32px;
    height: 32px;

    border:
        1px solid
        rgba(var(--border-tint-rgb), 0.20);

    border-radius: 10px;

    background: var(--panel);
    color: var(--muted-2);

    font-size: 18px;
    font-weight: 700;
}

.push-device-current .push-device-icon {
    color: var(--success-strong);
}

.push-device-info {
    min-width: 0;
}

.push-device-name {
    overflow: hidden;

    /* v58.39: было var(--header) - см. комментарий у .auth-card выше. */
    color: var(--text);

    font-size: 13px;
    font-weight: 750;
    line-height: 18px;

    text-overflow: ellipsis;
    white-space: nowrap;
}

.push-device-current-badge {
    display: inline-flex;
    align-items: center;

    padding: 1px 5px;

    border-radius: 999px;

    background:
        rgba(var(--success-rgb), 0.12);

    color: var(--success-strong);

    font-size: 10px;
    font-weight: 800;
    line-height: 15px;
    vertical-align: 1px;
}

.push-device-details,
.push-device-activity {
    overflow: hidden;

    color: var(--muted-2);

    font-size: 11px;
    line-height: 16px;

    text-overflow: ellipsis;
    white-space: nowrap;
}

.push-device-activity {
    color: var(--muted-3);
}

.push-device-remove {
    flex: 0 0 auto;

    min-height: 32px;

    padding: 6px 8px;

    border:
        1px solid
        rgba(var(--danger-rgb), 0.18);

    border-radius: 9px;

    background:
        rgba(var(--danger-soft-rgb), 0.92);

    color: var(--danger-strong);

    font: inherit;
    font-size: 11px;
    font-weight: 750;

    cursor: pointer;

    -webkit-tap-highlight-color:
        transparent;
}

.push-device-remove:active {
    transform: scale(0.97);
}

.push-device-remove:disabled {
    cursor: wait;
    opacity: 0.55;
}


/* Read receipts v57 */
.message-meta {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 5px;
}

.message-read-state {
    /*
     * v60.50 (задачи B/C): фиксированная ширина слота галочки, чтобы
     * ∅ -> ✓ (первый рендер -> сеть) и ✓ -> ✓✓ (доставлено ->
     * прочитано) не меняли геометрию строки и не дёргали ленту.
     * Ширины хватает на ✓✓ при letter-spacing -2px.
     */
    display: inline-block;
    min-width: 11px;
    text-align: right;
    color: var(--muted);
    font-size: 12px;
    font-weight: 700;
    letter-spacing: -2px;
}

.message-read-state.read {
    color: var(--info);
}

.message-readers {
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--info);
    font: inherit;
    cursor: pointer;
}

.message-readers:disabled {
    color: var(--muted);
    cursor: default;
}

.read-receipt-overlay {
    position: fixed;
    z-index: 1200;
    inset: 0;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    background: rgba(var(--shadow-rgb), 0.38);
    backdrop-filter: blur(3px);
}

.read-receipt-sheet {
    width: min(100%, 540px);
    max-height: min(72vh, 620px);
    overflow: hidden;
    border-radius: 22px 22px 0 0;
    background: var(--panel);
    box-shadow: 0 -18px 60px rgba(var(--shadow-rgb), 0.2);
    padding-bottom: env(safe-area-inset-bottom);
}

.read-receipt-handle {
    width: 38px;
    height: 4px;
    margin: 9px auto 3px;
    border-radius: 999px;
    background: var(--input-border);
}

.read-receipt-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 18px 10px;
    border-bottom: 1px solid rgba(var(--shadow-rgb), 0.08);
}

.read-receipt-close {
    padding: 6px 0;
    border: 0;
    background: transparent;
    color: var(--accent);
    font: inherit;
    font-weight: 600;
}

.read-receipt-list {
    max-height: calc(min(72vh, 620px) - 68px);
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 6px 18px 16px;
}

.read-receipt-user {
    display: flex;
    align-items: center;
    gap: 12px;
    min-height: 56px;
    border-bottom: 1px solid rgba(var(--shadow-rgb), 0.06);
}

.read-receipt-avatar {
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    width: 40px;
    height: 40px;
    overflow: hidden;
    border-radius: 50%;
    background: var(--panel-alt-border);
    color: var(--muted-strong);
    font-size: 13px;
    font-weight: 700;
}

.read-receipt-avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.read-receipt-name {
    min-width: 0;
    overflow: hidden;
    color: var(--text);
    font-size: 15px;
    font-weight: 600;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.read-receipt-empty {
    padding: 30px 10px;
    color: var(--muted);
    text-align: center;
}


/* Modern Chat hotfix v58.1
   v58.9.3: smooth убран — программные прокрутки мгновенны,
   кнопка "вниз" задаёт плавность явно в scrollTo(). */

.message-area {
    scroll-behavior: auto;
}

.message-separator {
    position: relative;
    z-index: 2;
    top: auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    margin: 12px 0 8px;
    pointer-events: none;
}

.message-separator span {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: max-content;
    max-width: calc(100% - 32px);
    min-height: 26px;
    padding: 4px 12px;
    border: 1px solid rgba(var(--border-tint-rgb), 0.18);
    border-radius: 999px;
    /* v60.39: убран backdrop-filter - разделители дней прокручиваются
       вместе с лентой, блюр backdrop'а пересчитывался постоянно. */
    background: rgba(var(--surface-rgb), 0.97);
    box-shadow: 0 2px 10px rgba(var(--shadow-rgb), 0.07);
    color: var(--muted-2);
    font-size: 11px;
    font-weight: 760;
    letter-spacing: 0.01em;
    line-height: 1.15;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.message-separator.unread-separator {
    position: relative;
    top: auto;
    margin: 15px 0 11px;
}

.message-separator.unread-separator::before,
.message-separator.unread-separator::after {
    content: '';
    height: 1px;
    flex: 1;
    background: linear-gradient(
        90deg,
        transparent,
        rgba(var(--accent-rgb), 0.32)
    );
}

.message-separator.unread-separator::after {
    background: linear-gradient(
        90deg,
        rgba(var(--accent-rgb), 0.32),
        transparent
    );
}

.message-separator.unread-separator span {
    margin: 0 10px;
    border-color: rgba(var(--accent-rgb), 0.18);
    background: rgba(var(--accent-soft-rgb), 0.94);
    color: var(--accent);
}

.message-row.message-row-enter {
    animation: modern-message-enter 180ms ease-out both;
}

.message-row.series-first {
    margin-top: 7px;
}

.message-row.series-middle,
.message-row.series-last:not(.series-first) {
    margin-top: 2px;
}

.message-row.series-middle .message-bubble,
.message-row.series-last:not(.series-first) .message-bubble {
    border-top-right-radius: 10px;
    border-top-left-radius: 10px;
}

.message-row.mine.series-middle .message-bubble {
    border-top-right-radius: 6px;
    border-bottom-right-radius: 6px;
}

.message-row.mine.series-first:not(.series-last) .message-bubble {
    border-bottom-right-radius: 6px;
}

.message-row.theirs.series-middle .message-bubble {
    border-top-left-radius: 6px;
    border-bottom-left-radius: 6px;
}

.message-row.theirs.series-first:not(.series-last) .message-bubble {
    border-bottom-left-radius: 6px;
}

.message-row.series-last .message-bubble {
    margin-bottom: 3px;
}

.message-bubble {
    padding: 8px 11px 6px;
    box-shadow:
        0 1px 1px rgba(var(--shadow-rgb), 0.06),
        0 3px 10px rgba(var(--shadow-rgb), 0.045);
    transition:
        transform 140ms cubic-bezier(.2,.8,.2,1),
        box-shadow 140ms ease,
        border-radius 140ms ease;
}

.message-meta {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 4px;
    min-height: 13px;
    margin-top: 3px;
    font-variant-numeric: tabular-nums;
}

/*
 * v60.18: если в meta есть лайк (первый flex-элемент, добавляется
 * в app.js), раздвигаем его к левому краю, а время/галочки - к
 * правому, вместо накопления всего у правого края. Время/галочки
 * теперь сгруппированы внутри .message-meta-time - конкретно этот
 * span не трогаем, только раздвигаем два верхнеуровневых ребёнка.
 */
.message-meta.has-like {
    justify-content: space-between;
}

.message-meta-time {
    display: flex;
    align-items: center;
    gap: 4px;
}

.message-avatar-slot {
    display: flex;
    /* v60.15: 30px -> 35px, вслед за .message-author-avatar 27px -> 32px
       (тот же зазор в 3px вокруг аватара, что был и раньше). */
    flex: 0 0 35px;
    align-self: flex-end;
    justify-content: center;
    width: 35px;
    margin-right: 3px;
}

.message-avatar-slot.empty {
    visibility: hidden;
}

.message-author-avatar.avatar {
    /*
     * v58.30: этот вариант (аватарка автора в групповом чате, 27px)
     * ни разу не получал фикс от овала - v58.28/v58.29 трогали
     * только .avatar/.avatar-small. Он наследует от .avatar тот же
     * display:grid + place-items:center без grid-template-columns/
     * rows своего размера (27px), значит был подвержен той же
     * ошибке (см. подробный разбор в .avatar выше). Добавлены
     * aspect-ratio + полный набор min/max + grid-template под 27px.
     *
     * v60.15: 27px -> 32px - сверка со скриншотами Telegram (группа)
     * показала, что аватар автора там заметно крупнее; средняя
     * уверенность (нет своего скриншота группы iChat для точного
     * сравнения), см. docs/04-visual-sizing-telegram-comparison.md.
     */
    aspect-ratio: 1;
    width: 32px;
    min-width: 32px;
    max-width: 32px;
    height: 32px;
    min-height: 32px;
    max-height: 32px;
    grid-template-columns: 32px;
    grid-template-rows: 32px;
    font-size: 9px;
    box-shadow: 0 1px 4px rgba(var(--shadow-rgb), 0.12);
}

.message-row.group-message .message-bubble {
    max-width: min(72%, 610px);
}

.message-sender {
    margin-bottom: 3px;
}

.message-quote {
    /*
     * v58.36: это ПОЗЖЕ по каскаду, чем первое .message-quote выше по
     * файлу (та же специфичность - побеждает последнее объявление),
     * поэтому именно border-radius:8px и эта заливка - то, что реально
     * применяется. background теперь через --quote-bg-alpha (см. :root
     * и dark-блок) - в тёмной теме подложка цитаты больше не сливается
     * с фоном исходящего пузыря.
     */
    border-radius: 8px;
    background: rgba(var(--accent-rgb), var(--quote-bg-alpha));
}

.scroll-to-bottom {
    position: absolute;
    z-index: 30;
    right: max(14px, env(safe-area-inset-right));
    bottom: calc(78px + env(safe-area-inset-bottom));
    display: inline-flex;
    align-items: center;
    gap: 7px;
    min-width: 44px;
    height: 44px;
    padding: 0 13px;
    border: 1px solid rgba(var(--border-tint-rgb), 0.2);
    border-radius: 22px;
    /* v60.39: убран backdrop-filter - кнопка висит поверх
       прокручиваемой ленты, блюр пересчитывался на каждом кадре. */
    background: rgba(var(--surface-rgb), 0.98);
    box-shadow: 0 8px 26px rgba(var(--shadow-rgb), 0.18);
    color: var(--muted-2);
    font: inherit;
    font-size: 12px;
    font-weight: 760;
    animation: modern-float-in 170ms ease-out both;
}

.scroll-to-bottom.has-new {
    border-color: rgba(var(--accent-rgb), 0.22);
    color: var(--accent);
}

.scroll-to-bottom-icon {
    font-size: 21px;
    line-height: 1;
}

.scroll-to-bottom-count {
    white-space: nowrap;
}

.message-menu-backdrop {
    animation: modern-backdrop-in 180ms ease-out both;
}

.message-menu-backdrop.closing {
    animation: modern-backdrop-out 180ms ease-in both;
    pointer-events: none;
}

.message-menu-sheet {
    /*
     * v58.21: было "overflow: hidden;" - это того же origin, что
     * и правило выше по файлу (первое ".message-menu-sheet"), но
     * идёт позже в источнике, поэтому побеждало по каскаду и
     * перебивало "overflow-y: auto;" из первого правила. Из-за
     * этого лист вообще не прокручивался (см. подробный комментарий
     * у .forward-picker-results выше). border-radius корректно
     * клипается и без overflow:hidden здесь, т.к. все дочерние
     * блоки (preview/actions/results) имеют отступы от края
     * (margin 9-10px) и не доходят до скруглённых углов.
     */
    overflow-y: auto;
    border: 1px solid rgba(var(--surface-rgb), 0.55);
    background: rgba(var(--surface-rgb), 0.96);
    box-shadow: 0 24px 70px rgba(var(--shadow-rgb), 0.3);
    backdrop-filter: blur(24px);
    animation: modern-sheet-in 220ms cubic-bezier(.2,.85,.2,1) both;
}

.message-menu-backdrop.closing .message-menu-sheet {
    animation: modern-sheet-out 170ms ease-in both;
}

.message-menu-preview {
    margin: 0 10px 10px;
    border: 1px solid rgba(var(--border-tint-rgb), 0.15);
    background: rgba(var(--surface-rgb), 0.78);

    /*
     * v58.37: --glass-highlight-rgb (белый в обеих темах), а не
     * --surface-rgb - это верхний "стеклянный" блик карточки, а не
     * заливка. --surface-rgb в тёмной теме сам тёмный, поэтому блик
     * той же тональности, что и фон, был не виден вовсе.
     */
    box-shadow: inset 0 1px 0 rgba(var(--glass-highlight-rgb), 0.75);
}

/*
 * v60.16: сетка 3x2 из круглых icon-tile (share-sheet-стиль) заменена
 * на компактный список строк "иконка+текст" - по прямому запросу
 * пользователя после сверки со скриншотом Telegram (там меню
 * сообщения - список, не сетка плиток). Сам bottom-sheet/backdrop/
 * превью цитируемого сообщения/кнопка "Отмена" не тронуты - просили
 * поменять только стиль списка действий.
 */
.message-menu-actions {
    display: flex;
    flex-direction: column;
    margin: 0 10px 10px;
    border: 1px solid rgba(var(--border-tint-rgb), 0.14);
    border-radius: 16px;
    background: rgba(var(--surface-rgb), 0.82);
    overflow: hidden;
}

.message-menu-actions button {
    /*
     * v60.18: убрали иконки-span у пунктов меню (были "отвратительные
     * и не современные" по прямому запросу пользователя) - строки
     * теперь чистый текст. gap:12px убран (не нужен без иконки),
     * min-height 50px -> 48px - без иконки строка визуально не
     * требовала прежней высоты, но осталась не мельче тап-зоны
     * .settings-row-button (48px) для консистентности с настройками.
     */
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: flex-start;
    min-height: 48px;
    padding: 0 16px;
    border: 0;
    border-bottom: 1px solid rgba(var(--border-tint-rgb), 0.14);
    background: transparent;
    font-size: 16px;
    text-align: left;
}

/*
 * Не убираю border-bottom у последней строки через :last-child/
 * :last-of-type: часть кнопок скрыта атрибутом hidden в зависимости
 * от сообщения (Редактировать/Удалить у всех - для чужих; Цитировать/
 * Переслать - для удалённых) и hidden-элемент всё равно считается
 * "последним ребёнком" в DOM, а не первый СКРЫТЫЙ - т.е. итоговая
 * видимая последняя строка не совпадала бы с CSS-последней и разделитель
 * оставался бы под ней. Проще и надёжнее оставить разделитель у каждой
 * строки - лишний 1px у самого низа списка сливается с внешней рамкой
 * контейнера (та же rgba(border-tint,.14)) и практически не заметен.
 */

.message-menu-cancel {
    /*
     * v60.18: у верхнего (более раннего) правила той же специфичности
     * был отдельный отступ сверху у этой кнопки, но нижний shorthand
     * ниже по файлу перебивал именно верхнюю сторону нулём (см. разбор
     * каскада в комментарии выше по файлу) - кнопка "Отмена" сидела
     * вплотную к списку действий без зазора. Указываем верхний отступ
     * явно тем же значением, что и остальные стороны - органичные
     * симметричные отступы вокруг кнопки со всех сторон.
     */
    margin: 10px 10px 10px;
    width: calc(100% - 20px);
    border-radius: 15px;
    background: rgba(var(--surface-rgb), 0.9);
    font-weight: 760;
}

.message-bubble::after {
    right: -47px;
    width: 34px;
    height: 34px;
    border-color: rgba(var(--accent-rgb), 0.16);
    box-shadow: 0 5px 16px rgba(var(--shadow-rgb), 0.16);
    font-size: 19px;
    transform: translateY(-50%) scale(calc(.82 + var(--reply-swipe-progress, 0) * .22));
}

.message-row.reply-swipe-armed .message-bubble {
    box-shadow: 0 7px 22px rgba(var(--accent-rgb), 0.16);
}

@keyframes modern-message-enter {
    from { opacity: 0; transform: translateY(5px) scale(.995); }
    to { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes modern-float-in {
    from { opacity: 0; transform: translateY(7px) scale(.94); }
    to { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes modern-backdrop-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes modern-backdrop-out { from { opacity: 1; } to { opacity: 0; } }
@keyframes modern-sheet-in {
    from { opacity: 0; transform: translateY(28px) scale(.985); }
    to { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes modern-sheet-out {
    from { opacity: 1; transform: translateY(0) scale(1); }
    to { opacity: 0; transform: translateY(22px) scale(.99); }
}

@media (max-width: 520px) {
    /* v60.16: раньше здесь было grid-template-columns для .message-menu-actions -
       больше не нужно, список действий теперь не grid (см. .message-menu-actions выше). */

    .message-row.group-message .message-bubble {
        max-width: calc(100% - 72px);
    }
}

@media (prefers-reduced-motion: reduce) {
    .message-row,
    .scroll-to-bottom,
    .message-menu-backdrop,
    .message-menu-sheet {
        animation-duration: 1ms !important;
    }
}

/* История: поиск по переписке открытого чата (v58.12) */

.history-search-button {
    font-size: 20px;
}

.history-search-panel {
    display: flex;
    flex-direction: column;
    max-height: min(60vh, 420px);
    background: var(--panel);

    /*
     * v58.14: раньше поле поиска (само скруглённое) сидело
     * в панели с прямыми углами и жёсткой линией-разделителем -
     * визуально выглядело как "прямоугольник вокруг круглого
     * поля". Теперь панель сама скруглена снизу и отделяется
     * мягкой тенью - читается как единая плавающая карточка,
     * в тон пузырям сообщений и карточкам чатов.
     *
     * v58.15: overflow:hidden убран отсюда - в связке с
     * flex-direction:column + max-height + flex:1-ребёнком
     * (.history-search-results) это давало заниженную высоту
     * auto-контейнера в WebKit/Safari и обрезало строку поиска
     * примерно наполовину. Скругление нижних углов перенесено
     * на сам список результатов ниже - тот же визуальный эффект
     * без этой связки багов.
     */
    border-radius: 0 0 18px 18px;
    box-shadow: var(--shadow);
}

.history-search-bar {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    gap: 6px;
    padding: 10px 14px;
}

.history-search-input {
    flex: 1;
    min-width: 0;
    height: 40px;
    padding: 0 14px;
    border: 1px solid var(--input-border);
    border-radius: 20px;
    outline: none;
    background: var(--input-bg);
    color: var(--text);
    /* 16px - обязательный минимум против zoom в Safari на iPhone. */
    font-size: 16px;
}

.history-search-input:focus {
    border-color: var(--accent);
}

.history-search-close {
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    width: 44px;
    height: 44px;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--muted);
    font-size: 16px;
    cursor: pointer;
}

.history-search-close:active {
    background: rgba(var(--shadow-rgb), 0.06);
}

.history-search-results {
    flex: 1;
    overflow-y: auto;
    padding-bottom: 6px;
    border-radius: 0 0 18px 18px;
}

.history-search-result-row {
    display: flex;
    flex-direction: column;
    gap: 3px;
    width: 100%;
    min-height: 44px;
    padding: 9px 16px;
    border: 0;
    border-bottom: 1px solid var(--border);
    background: transparent;
    color: inherit;
    text-align: left;
    cursor: pointer;
}

.history-search-result-row:active,
.history-search-result-row.active {
    background: rgba(var(--accent-rgb), 0.07);
}

.history-search-result-meta {
    color: var(--muted);
    font-size: 12px;
}

.history-search-result-snippet {
    overflow: hidden;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    font-size: 14px;
    line-height: 1.35;
}

.history-search-highlight {
    padding: 0 1px;
    background: rgba(var(--accent-rgb), 0.18);
    color: inherit;
    font-weight: 650;
}

.history-search-load-more {
    display: block;
    width: 100%;
    padding: 12px 16px;
    border: 0;
    background: transparent;
    color: var(--accent);
    font-size: 14px;
    font-weight: 600;
    text-align: center;
    cursor: pointer;
}

.history-search-load-more:disabled {
    color: var(--muted);
    cursor: wait;
}

.history-search-compact {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    gap: 8px;
    min-height: 44px;
    padding: 6px 12px;
    background: var(--panel);
    border-radius: 0 0 18px 18px;
    box-shadow: var(--shadow);
    cursor: pointer;
}

.history-search-compact-icon {
    flex: 0 0 auto;
    font-size: 15px;
}

.history-search-compact-query {
    overflow: hidden;
    flex: 1 1 auto;
    min-width: 0;
    color: var(--text);
    font-size: 14px;
    font-weight: 600;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.history-search-compact-index {
    flex: 0 0 auto;
    color: var(--muted);
    font-size: 13px;
}

.history-search-nav-btn {
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    width: 36px;
    height: 36px;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--accent);
    font-size: 16px;
    cursor: pointer;
}

.history-search-nav-btn:disabled {
    color: var(--muted);
    cursor: default;
    opacity: 0.5;
}

.history-search-nav-btn:active:not(:disabled) {
    background: rgba(var(--accent-rgb), 0.1);
}

/*
 * Подсветка результата поиска - тот же приём, что и у
 * push-target-message (v58.9.5): обычный transition вместо
 * @keyframes, box-shadow уже транзиционится у .message-bubble,
 * поэтому подсветка не мигает заново при пересборке строки.
 * Параллельный, независимый класс - не пересекается с push.
 */
.message-row.search-target-message .message-bubble {
    box-shadow:
        0 0 0 3px rgba(var(--accent-rgb), 0.22),
        0 8px 24px rgba(var(--accent-rgb), 0.14);
}

/*
 * v58.43: экран настроек. Полноэкранный оверлей поверх .app-shell
 * (position:fixed, inset:0) - НЕ третья колонка в grid-раскладке
 * .app-shell, см. подробный комментарий в index.html у #settingsScreen
 * о причине такого выбора. z-index выше .push-controls (5000), чтобы
 * плавающий колокольчик не оказался поверх Настроек, если открыт
 * прямо перед переходом.
 */
.settings-screen {
    position: fixed;
    z-index: 6000;
    inset: 0;

    display: flex;
    flex-direction: column;

    height: 100svh;

    background: var(--panel);
}

.settings-screen.hidden {
    display: none !important;
}

body.settings-open {
    overflow: hidden;
}

.settings-header {
    flex: 0 0 auto;
}

.settings-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;

    padding:
        20px
        16px
        calc(24px + env(safe-area-inset-bottom));

    display: flex;
    flex-direction: column;
    gap: 28px;
}

.settings-profile {
    display: flex;
    align-items: center;
    gap: 16px;

    padding: 4px;
}

.settings-profile-info {
    min-width: 0;
}

.settings-profile-name {
    overflow: hidden;

    color: var(--text);

    font-size: 19px;
    font-weight: 700;
    line-height: 24px;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.settings-profile-position {
    margin-top: 2px;
    overflow: hidden;

    color: var(--muted-2);

    font-size: 14px;
    line-height: 19px;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.settings-section-title {
    margin-bottom: 10px;

    color: var(--muted-2);

    font-size: 13px;
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: uppercase;
}

/*
 * v58.45: строки-пункты секции "Общие" (тема оформления, обновить) -
 * раньше это были отдельные кнопки-иконки в шапке списка чатов,
 * теперь обычные горизонтальные строки внутри секции настроек.
 * Соседние строки внутри одной секции разделены тонкой границей
 * (border-top у всех кроме первой), без отдельных карточек - как
 * стандартные группы настроек в Telegram.
 */
.settings-row-button {
    display: flex;
    align-items: center;
    gap: 12px;

    width: 100%;
    min-height: 48px;

    padding: 12px 4px;

    border: 0;
    border-top: 1px solid var(--border);
    background: transparent;
    color: var(--text);

    font: inherit;
    font-size: 15px;
    text-align: left;

    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.settings-row-button:first-child {
    border-top: 0;
}

.settings-row-button:active {
    background: rgba(var(--ink-tint-rgb), 0.06);
}

.settings-row-icon {
    flex: 0 0 auto;
    font-size: 19px;
    line-height: 1;
}

.settings-row-label {
    flex: 1 1 auto;
    min-width: 0;
}

.settings-row-value {
    flex: 0 0 auto;
    color: var(--muted-2);
    font-size: 14px;
}

/*
 * Копия пуш-панели внутри Настроек переиспользует существующие
 * .push-status/.push-devices/.push-actions/.push-action-button и
 * т.п. классы напрямую (см. index.html) - собственных стилей для
 * содержимого не требуется, .settings-push-status ниже только убирает
 * отрицательный нижний отступ, рассчитанный на floating dropdown
 * (там .push-status - часть плавающей карточки со своим padding,
 * здесь - часть обычного потока страницы).
 */
.settings-push-status {
    margin-bottom: 10px;
}

.settings-logout-button {
    width: 100%;

    min-height: 46px;

    padding: 12px 16px;

    border: 1px solid rgba(var(--danger-rgb), 0.24);
    border-radius: 12px;

    background: transparent;
    color: var(--danger-strong);

    font: inherit;
    font-size: 15px;
    font-weight: 650;

    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.settings-logout-button:active {
    background: rgba(var(--danger-rgb), 0.08);
}

/*
 * v58.52: ВРЕМЕННЫЙ диагностический оверлей (task #96/#100) - см.
 * подробный комментарий в index.html. Полностью изолирован от
 * остальной вёрстки (position:fixed, максимальный z-index, не влияет
 * на поток документа и ни на что не завязан кроме собственных id).
 * Планируется убрать после диагностики.
 */
.debug-viewport-toggle {
    position: fixed;
    top: calc(8px + env(safe-area-inset-top));
    right: 8px;
    z-index: 999999;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    border: 1px solid var(--border);
    background: var(--panel);
    color: var(--text);
    font: 700 14px/32px monospace;
    text-align: center;
    padding: 0;
    cursor: pointer;
    box-shadow: 0 1px 4px rgba(var(--shadow-rgb), 0.3);
}

.debug-viewport-panel {
    position: fixed;
    top: calc(46px + env(safe-area-inset-top));
    right: 8px;
    z-index: 999999;
    max-width: 280px;
    max-height: 70vh;
    overflow-y: auto;
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 10px;
    box-shadow: 0 2px 10px rgba(var(--shadow-rgb), 0.35);
}

.debug-viewport-panel.hidden {
    display: none;
}

.debug-viewport-output {
    margin: 0 0 8px;
    font: 11px/1.4 monospace;
    color: var(--text);
    white-space: pre-wrap;
    word-break: break-word;
}

.debug-viewport-copy-button {
    width: 100%;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--bg);
    color: var(--text);
    font-size: 12px;
    padding: 6px;
    cursor: pointer;
}


/* v58.55: temporary diagnostics and bottom tab navigation removed. */
.chat-list {
    height: calc(
        100svh -
        var(--chat-list-header-offset, 158px) -
        env(safe-area-inset-top)
    );
    padding-bottom: env(safe-area-inset-bottom);
    box-sizing: border-box;
}

.sidebar-settings-button {
    width: 40px;
    height: 40px;
}

.sidebar-settings-button svg {
    width: 22px;
    height: 22px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
}


/* v58.56: conversation overflow menu and header search mode. */
.conversation-overflow-wrap { position: relative; flex: 0 0 auto; }
.conversation-overflow-button {
    width: 40px;
    height: 40px;
    /* v60.22: тот же видимый фон в покое, что и у .back-button выше -
       раньше эта кнопка (три точки) была полностью прозрачной, как и
       .back-button, и по той же причине выглядела "никак не
       изменившейся" даже после утолщения самих точек в v60.21. */
    border: 1px solid rgba(var(--border-tint-rgb), 0.24);
    background: linear-gradient(
        180deg,
        rgba(var(--surface-rgb), 0.98),
        rgba(var(--surface-rgb), 0.94)
    );
    box-shadow:
        0 1px 2px rgba(var(--shadow-rgb), 0.06),
        0 6px 18px rgba(var(--shadow-rgb), 0.08);
}
.conversation-overflow-button svg { width: 22px; height: 22px; fill: currentColor; }
.conversation-overflow-menu {
    position: absolute; top: calc(100% + 8px); right: 0; z-index: 80;
    width: min(250px, calc(100vw - 32px)); padding: 7px;
    border: 1px solid rgba(15, 23, 42, 0.09); border-radius: 16px;
    background: var(--panel); box-shadow: 0 18px 48px rgba(15, 23, 42, 0.18);
}
.conversation-overflow-menu button {
    display: flex; align-items: center; gap: 12px; width: 100%; min-height: 46px;
    padding: 0 12px; border: 0; border-radius: 11px; background: transparent;
    color: var(--text); font: inherit; text-align: left; cursor: pointer;
}
.conversation-overflow-menu button:active { background: var(--panel-muted); }
.conversation-overflow-menu button > span:first-child {
    display: grid; place-items: center; width: 24px; color: var(--text-muted); font-size: 20px;
}
.conversation-search-mode > .conversation-header:not(.selection-bar) { display: none; }
.conversation-search-mode .history-search-panel {
    order: -1; padding-top: calc(10px + env(safe-area-inset-top)); border-top: 0;
}
.conversation-search-mode .history-search-bar { min-height: 46px; }


/* v58.58: Telegram-like chat-list search and conversation overflow menu. */
#chatListSearchToggleButton { display: none !important; }

.chat-search {
    position: relative;
    z-index: 2;
    flex: 0 0 auto;
    overflow: hidden;
    transition: max-height 180ms cubic-bezier(.2,.8,.2,1), opacity 140ms ease, transform 180ms cubic-bezier(.2,.8,.2,1), padding 180ms cubic-bezier(.2,.8,.2,1);
}

.chat-search-field { border-radius: 12px; }

.conversation-overflow-menu {
    width: min(248px, calc(100vw - 24px));
    padding: 6px;
    border: 1px solid rgba(15,23,42,.08);
    border-radius: 12px;
    background: var(--panel);
    box-shadow: 0 12px 34px rgba(15,23,42,.18), 0 2px 8px rgba(15,23,42,.08);
    transform-origin: top right;
    animation: telegram-overflow-in 130ms cubic-bezier(.2,.8,.2,1);
}

@keyframes telegram-overflow-in {
    from { opacity: 0; transform: translateY(-3px) scale(.98); }
    to { opacity: 1; transform: translateY(0) scale(1); }
}

.conversation-overflow-menu button {
    display: flex;
    align-items: center;
    width: 100%;
    min-height: 44px;
    gap: 12px;
    padding: 8px 10px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--text);
    text-align: left;
    font: inherit;
    font-size: 15px;
    font-weight: 500;
    cursor: pointer;
}

.conversation-overflow-menu button:hover,
.conversation-overflow-menu button:active { background: var(--panel-muted); }
.conversation-overflow-menu button:active { transform: scale(.99); }

.conversation-overflow-icon {
    display: grid;
    place-items: center;
    flex: 0 0 22px;
    width: 22px;
    height: 22px;
    background: transparent !important;
    color: var(--text-muted);
}

.conversation-overflow-icon svg {
    width: 20px;
    height: 20px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.conversation-overflow-label { min-width: 0; flex: 1 1 auto; }

@media (prefers-color-scheme: dark) {
    .conversation-overflow-menu {
        border-color: rgba(255,255,255,.08);
        box-shadow: 0 14px 38px rgba(0,0,0,.42), 0 2px 8px rgba(0,0,0,.24);
    }
}


/* v58.59: Telegram pull-down chat search. */
.chat-search.chat-search-pulling {
    pointer-events: none;
    transition: none !important;
    will-change: max-height, padding, opacity, transform;
}

.chat-search.chat-search-pulling .chat-search-field,
.chat-search.chat-search-pulling .chat-search-clear {
    pointer-events: none;
}

@media (pointer: coarse), (max-width: 900px) {
    .chat-list {
        overscroll-behavior-y: contain;
        touch-action: pan-y;
    }
}


/* v58.60: compact notification and settings sheets. */
:root {
    --ichat-sheet-radius: 22px;
    --ichat-sheet-edge: 12px;
}

.push-panel {
    width: min(336px, calc(100vw - 24px)) !important;
    padding: 14px !important;
    border-radius: var(--ichat-sheet-radius) !important;
    border-color: rgba(var(--border-tint-rgb), .16) !important;
    box-shadow: 0 18px 50px rgba(var(--shadow-rgb), .20) !important;
}

.push-panel-title {
    margin-bottom: 4px !important;
    font-size: 17px !important;
    line-height: 22px !important;
}

.push-status {
    min-height: 0 !important;
    margin-bottom: 10px !important;
    font-size: 13px !important;
    line-height: 18px !important;
}

.push-devices {
    gap: 4px !important;
    max-height: 210px !important;
    margin-bottom: 8px !important;
    padding-top: 8px !important;
}

.push-devices-title {
    margin: 0 8px 2px !important;
    font-size: 10px !important;
    letter-spacing: .045em !important;
}

.push-device-item {
    grid-template-columns: 28px minmax(0, 1fr) auto !important;
    gap: 9px !important;
    padding: 8px 6px !important;
    border: 0 !important;
    border-radius: 11px !important;
    background: transparent !important;
}

.push-device-item + .push-device-item {
    border-top: 1px solid rgba(var(--border-tint-rgb), .14) !important;
    border-radius: 0 !important;
}

.push-device-current {
    background: rgba(var(--success-rgb), .07) !important;
}

.push-device-icon {
    width: 28px !important;
    height: 34px !important;
    border: 0 !important;
    border-radius: 0 !important;
    background: transparent !important;
    color: var(--muted-strong) !important;
}

.push-device-icon svg {
    width: 23px;
    height: 23px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.push-device-current .push-device-icon {
    color: var(--success-strong) !important;
}

.push-device-name {
    font-size: 13px !important;
    line-height: 17px !important;
}

.push-device-details,
.push-device-activity {
    font-size: 10.5px !important;
    line-height: 14px !important;
}

.push-device-current-badge {
    padding: 0 5px !important;
    font-size: 9px !important;
    line-height: 14px !important;
}

.push-device-remove {
    min-height: 30px !important;
    padding: 5px 7px !important;
    border: 0 !important;
    background: transparent !important;
    font-size: 11px !important;
}

.push-actions {
    gap: 2px !important;
    border-top: 1px solid rgba(var(--border-tint-rgb), .14);
    padding-top: 6px;
}

.push-action-button {
    min-height: 40px !important;
    padding: 8px 10px !important;
    border: 0 !important;
    border-radius: 10px !important;
    background: transparent !important;
    box-shadow: none !important;
    text-align: left;
    color: var(--text) !important;
    font-size: 14px !important;
    font-weight: 600 !important;
}

.push-action-button:active {
    background: rgba(var(--ink-tint-rgb), .06) !important;
}

.push-action-primary {
    color: var(--accent) !important;
}

.push-action-danger {
    color: var(--danger-strong) !important;
}

.settings-screen {
    inset: 0 0 0 auto !important;
    width: min(430px, 100vw) !important;
    border-left: 1px solid rgba(var(--border-tint-rgb), .15);
    box-shadow: -16px 0 48px rgba(var(--shadow-rgb), .16);
    animation: ichat-settings-sheet-in 180ms cubic-bezier(.2,.8,.2,1);
}

@keyframes ichat-settings-sheet-in {
    from { opacity: 0; transform: translateX(18px); }
    to { opacity: 1; transform: translateX(0); }
}

.settings-body {
    gap: 18px !important;
    padding: 14px 14px calc(18px + env(safe-area-inset-bottom)) !important;
}

.settings-profile {
    gap: 13px !important;
    padding: 8px 6px !important;
}

.settings-section {
    overflow: hidden;
    padding: 0 10px;
    border: 1px solid rgba(var(--border-tint-rgb), .13);
    border-radius: 16px;
    background: rgba(var(--surface-rgb), .72);
}

.settings-section-title {
    margin: 0 -10px 2px !important;
    padding: 10px 12px 5px;
    font-size: 11px !important;
    background: transparent;
}

.settings-row-button {
    min-height: 46px !important;
    padding: 10px 2px !important;
}

.settings-row-icon {
    display: grid;
    place-items: center;
    width: 26px;
    font-size: 18px !important;
}

.settings-push-status {
    margin: 0 2px 6px !important;
}

.settings-section .push-devices {
    max-height: none !important;
}

.settings-section .push-actions {
    margin-bottom: 5px;
}

.settings-logout-button {
    min-height: 44px !important;
    border: 0 !important;
}

/* Keep the floating panel inside every viewport, including iPhone safe areas. */
@media (max-width: 640px) {
    .push-panel {
        position: fixed !important;
        top: calc(58px + env(safe-area-inset-top)) !important;
        right: max(var(--ichat-sheet-edge), env(safe-area-inset-right)) !important;
        left: max(var(--ichat-sheet-edge), env(safe-area-inset-left)) !important;
        width: auto !important;
        max-height: calc(100svh - 76px - env(safe-area-inset-top) - env(safe-area-inset-bottom));
        overflow-y: auto;
    }

    .settings-screen {
        top: max(72px, 12svh) !important;
        right: max(8px, env(safe-area-inset-right)) !important;
        bottom: 0 !important;
        left: max(8px, env(safe-area-inset-left)) !important;
        width: auto !important;
        border: 1px solid rgba(var(--border-tint-rgb), .14);
        border-bottom: 0;
        border-radius: 24px 24px 0 0;
        box-shadow: 0 -14px 48px rgba(var(--shadow-rgb), .24);
        animation-name: ichat-settings-mobile-in;
    }

    .settings-screen::before {
        content: '';
        position: absolute;
        z-index: 4;
        top: 7px;
        left: 50%;
        width: 38px;
        height: 4px;
        border-radius: 999px;
        background: rgba(var(--ink-tint-rgb), .22);
        transform: translateX(-50%);
        pointer-events: none;
    }

    .settings-header {
        padding-top: 7px;
        border-radius: 24px 24px 0 0;
    }

    @keyframes ichat-settings-mobile-in {
        from { opacity: 0; transform: translateY(30px); }
        to { opacity: 1; transform: translateY(0); }
    }

    .chat-list {
        padding-right: max(10px, env(safe-area-inset-right)) !important;
        padding-left: max(10px, env(safe-area-inset-left)) !important;
    }
}


/* iChat compact panels v58.61: structural redesign matching approved mockup */
.push-panel {
    width: min(310px, calc(100vw - max(24px, env(safe-area-inset-left)) - max(24px, env(safe-area-inset-right)))) !important;
    padding: 0 !important;
    overflow: hidden;
    border-radius: 18px !important;
    background: var(--panel) !important;
    box-shadow: 0 18px 52px rgba(var(--shadow-rgb), .20) !important;
}
.push-panel-heading {
    display:flex; align-items:center; justify-content:space-between;
    min-height:54px; padding:0 16px;
}
.push-panel-title { margin:0 !important; font-size:16px !important; font-weight:780 !important; }
.push-panel-menu { border:0; background:transparent; color:var(--muted-2); font:800 15px/1 inherit; letter-spacing:2px; padding:8px 0; pointer-events:none; }
/* v60.16: 12px -> 13px, статусная строка "Уведомления включены..." была мельче остального. */
.push-status {
    position:relative; margin:0 !important; padding:0 16px 14px 44px !important;
    color:var(--text) !important; font-size:13px !important; line-height:18px !important;
    border-bottom:1px solid rgba(var(--border-tint-rgb),.18);
}
.push-status::before {
    content:'✓'; position:absolute; left:17px; top:-1px;
    display:grid; place-items:center; width:16px; height:16px;
    border:2px solid var(--success); border-radius:50%; color:var(--success);
    font-size:10px; font-weight:900; box-sizing:border-box;
}
.push-status[data-status="warning"]::before { content:'!'; border-color:var(--warning); color:var(--warning); }
.push-status[data-status="error"]::before { content:'!'; border-color:var(--danger); color:var(--danger); }
.push-devices { gap:0 !important; max-height:210px !important; margin:0 !important; padding:0 !important; border-top:0 !important; overflow-y:auto; }
/* v60.16: 11px -> 12px, заголовок "Подключённые устройства" был мельче остального. */
.push-devices-title { margin:0 !important; padding:14px 16px 8px !important; font-size:12px !important; line-height:15px !important; letter-spacing:.02em !important; text-transform:none !important; }
.push-device-item {
    grid-template-columns:34px minmax(0,1fr) auto !important; gap:10px !important;
    min-height:62px; padding:10px 16px !important;
    border:0 !important; border-radius:0 !important; background:transparent !important;
    border-top:1px solid rgba(var(--border-tint-rgb),.14) !important;
}
.push-device-item:first-of-type { border-top:0 !important; }
.push-device-icon { width:34px !important; height:34px !important; border-radius:10px !important; background:rgba(var(--ink-tint-rgb),.035) !important; }
.push-device-icon svg { width:19px; height:19px; fill:none; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; }
.push-device-current { border-color:rgba(var(--border-tint-rgb),.14) !important; }
/* v60.16: имя устройства 13->14, бейдж "активно" 9->11 (был самым
   мелким текстом во всём приложении - до одного из unread-badge,
   11px, тот же порядок величины), подпись модели/ОС 11->12. */
.push-device-name { font-size:14px !important; line-height:17px !important; }
.push-device-current-badge { margin-left:4px; padding:1px 5px !important; font-size:11px !important; line-height:14px !important; }
.push-device-details { font-size:12px !important; line-height:15px !important; }
.push-device-activity { display:none !important; }
/* v60.16: 11px -> 12px, вслед за остальным текстом карточки устройства. */
.push-device-remove { min-height:30px !important; padding:5px 8px !important; border:0 !important; background:transparent !important; font-size:12px !important; }
.push-actions { display:flex !important; flex-direction:column !important; gap:0 !important; margin:0 !important; border-top:1px solid rgba(var(--border-tint-rgb),.18); }
.push-action-button {
    display:grid !important; grid-template-columns:22px minmax(0,1fr) auto; align-items:center; gap:10px;
    width:100%; min-height:52px !important; margin:0 !important; padding:0 16px !important;
    border:0 !important; border-top:1px solid rgba(var(--border-tint-rgb),.14) !important;
    border-radius:0 !important; background:transparent !important; box-shadow:none !important;
    color:var(--text) !important; font-size:13px !important; font-weight:600 !important; text-align:left;
}
.push-action-button.hidden { display:none !important; }
.push-action-button:first-child { border-top:0 !important; }
.push-action-button.push-action-danger { color:var(--danger-strong) !important; }
.push-action-icon { display:grid; place-items:center; width:22px; height:22px; color:currentColor; }
.push-action-icon svg { width:19px; height:19px; fill:none; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; }
.push-action-button:active { transform:none !important; background:rgba(var(--ink-tint-rgb),.055) !important; }

@media (max-width:640px) {
  .push-panel {
    right: calc(-48px + max(12px, env(safe-area-inset-right))) !important;
    max-width: calc(100vw - 24px - env(safe-area-inset-left) - env(safe-area-inset-right)) !important;
  }
}

/* Settings is now a real compact panel, not a stretched page */
.settings-screen {
    align-items:flex-end;
    padding: max(8px, env(safe-area-inset-top)) max(8px, env(safe-area-inset-right)) max(8px, env(safe-area-inset-bottom)) max(8px, env(safe-area-inset-left));
    background:rgba(var(--shadow-rgb),.20) !important;
    backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px);
}
.settings-header,
.settings-body { width:min(390px,100%); box-sizing:border-box; background:var(--panel); }
.settings-header { border-radius:22px 22px 0 0; border-bottom:1px solid rgba(var(--border-tint-rgb),.16); }
.settings-body {
    flex:0 1 auto !important; max-height:calc(100svh - 70px); gap:14px !important;
    padding:14px 12px calc(16px + env(safe-area-inset-bottom)) !important;
    border-radius:0 0 22px 22px; box-shadow:0 24px 70px rgba(var(--shadow-rgb),.22);
}
.settings-profile { padding:10px 8px 14px !important; gap:12px !important; }
/* v60.15: 50px -> 88px - этот override занижал .avatar-large обратно
   до размера меньше даже списка чатов; 88px - та же база, что и у
   .avatar-large в остальных местах, см. docs/04-visual-sizing-
   telegram-comparison.md (самое заметное расхождение со скриншотом Telegram). */
.settings-profile .avatar-large { width:88px; height:88px; }
.settings-profile-name { font-size:20px !important; line-height:25px !important; }
.settings-profile-position { font-size:13px !important; line-height:18px !important; }
.settings-section {
    overflow:hidden; border:1px solid rgba(var(--border-tint-rgb),.16); border-radius:15px;
    background:rgba(var(--surface-rgb),.72);
}
/* v60.16: 11px -> 12px, заголовки секций ("Общие"/"Уведомления") были мельче остального. */
.settings-section-title { margin:0 !important; padding:11px 14px 7px; font-size:12px !important; line-height:15px; text-transform:none !important; }
.settings-row-button { min-height:48px !important; padding:0 14px !important; gap:11px !important; border-top-color:rgba(var(--border-tint-rgb),.14) !important; }
/* v60.15: 24px -> 30px / svg 20px -> 22px - это правило (внутри
   @media max-width:640px) побеждает на телефоне последним в каскаде
   и реально задаёт размер иконок строк настроек; было заметно мельче,
   чем у Telegram, см. docs/04-visual-sizing-telegram-comparison.md. */
.settings-row-icon { display:grid; place-items:center; width:30px; height:30px; color:var(--accent); }
.settings-row-icon svg { width:22px; height:22px; fill:none; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round; }
/* v60.16: label 14->15, value 12->13 - подтянуто вслед за остальной типографикой настроек. */
.settings-row-label { font-size:15px; font-weight:600; }
.settings-row-value { margin-left:auto; font-size:13px !important; }
.settings-section .settings-push-status { padding:10px 14px 12px 40px !important; }
.settings-section .settings-push-status::before { left:14px; top:10px; }
.settings-section .push-devices-title { padding-left:14px !important; }
.settings-section .push-device-item { padding-left:14px !important; padding-right:14px !important; }
.settings-section .push-action-button { padding-left:14px !important; padding-right:14px !important; }
.settings-logout-button { min-height:50px !important; border:0 !important; border-radius:0 !important; background:transparent !important; font-size:14px !important; }

/* Real right breathing room for chat list and floating panel border */
@media (max-width:780px) {
  .chat-list { padding-right:max(10px, env(safe-area-inset-right)) !important; box-sizing:border-box; }
  .chat-item { margin-right:0 !important; }
}


/* v58.67: anchor the notification panel to the viewport on phones.
 * The panel used to be positioned relative to #pushControls, which sits before the
 * settings button. On iPhone this made the whole card appear shifted to the left.
 * Fixed positioning keeps the card flush with the visible right screen edge. */
@media (max-width: 640px) {
    .push-panel {
        position: fixed !important;
        top: calc(108px + env(safe-area-inset-top)) !important;
        right: max(12px, env(safe-area-inset-right)) !important;
        left: auto !important;
        width: min(310px, calc(100vw - 24px - env(safe-area-inset-left) - env(safe-area-inset-right))) !important;
        max-width: none !important;
        z-index: 1200 !important;
    }
}


/* v58.68: visually unify the push panel and remove redundant divider lines. */
.push-panel-heading {
    padding: 4px 18px 0 !important;
}

.push-status {
    padding: 4px 18px 18px 48px !important;
    border-bottom: 0 !important;
}

.push-status::before {
    left: 19px !important;
    top: 4px !important;
}

.push-devices {
    margin: 0 12px 10px !important;
    border: 0 !important;
    border-radius: 14px !important;
    background: rgba(var(--surface-rgb), .58) !important;
    overflow: hidden auto !important;
}

.push-devices-title {
    padding: 13px 14px 7px !important;
}

.push-device-item,
.push-device-item:first-of-type,
.push-device-current {
    border: 0 !important;
}

.push-device-item {
    padding: 10px 14px 13px !important;
}

.push-actions {
    gap: 6px !important;
    padding: 0 12px 12px !important;
    border-top: 0 !important;
}

.push-action-button,
.push-action-button:first-child {
    min-height: 48px !important;
    padding: 0 14px !important;
    border: 0 !important;
    border-radius: 12px !important;
    background: rgba(var(--surface-rgb), .46) !important;
}

.push-action-button.push-action-danger {
    background: rgba(220, 38, 38, .045) !important;
}

.push-action-button:active {
    background: rgba(var(--ink-tint-rgb), .075) !important;
}


/* v58.70: settings sheet mobile layout and scrolling repair.
 * The previous compact-panel overrides made the body shrink to content while
 * the outer sheet also had its own mobile offsets. On iPhone this clipped the
 * notification section and logout card. Keep one coherent sheet, one scroll
 * container, and compact non-wrapping push actions inside Settings only. */
.settings-screen {
    box-sizing: border-box;
}

.settings-header,
.settings-body {
    min-width: 0;
}

.settings-body {
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    scrollbar-gutter: stable;
}

.settings-section .push-actions {
    border-top: 0 !important;
    padding: 4px 8px 8px !important;
    gap: 6px !important;
}

.settings-section .push-action-button {
    display: flex !important;
    align-items: center;
    justify-content: flex-start;
    grid-template-columns: none !important;
    min-height: 46px !important;
    padding: 0 12px !important;
    border: 0 !important;
    border-radius: 12px !important;
    background: rgba(var(--ink-tint-rgb), 0.035) !important;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.settings-section .push-action-button.push-action-danger {
    background: rgba(var(--danger-rgb), 0.055) !important;
}

.settings-section .push-devices {
    overflow: visible !important;
}

@media (max-width: 640px) {
    .settings-screen {
        inset: 0 !important;
        width: 100% !important;
        height: 100svh !important;
        padding:
            max(76px, calc(env(safe-area-inset-top) + 58px))
            max(10px, env(safe-area-inset-right))
            0
            max(10px, env(safe-area-inset-left)) !important;
        align-items: stretch !important;
        justify-content: flex-start !important;
        overflow: hidden !important;
        border: 0 !important;
        border-radius: 0 !important;
        background: rgba(var(--shadow-rgb), 0.20) !important;
    }

    .settings-screen::before {
        top: calc(max(76px, calc(env(safe-area-inset-top) + 58px)) + 8px) !important;
    }

    .settings-header,
    .settings-body {
        width: 100% !important;
        max-width: none !important;
    }

    .settings-header {
        flex: 0 0 auto !important;
        min-height: 62px;
        padding-top: 10px !important;
        border-radius: 24px 24px 0 0 !important;
    }

    .settings-body {
        flex: 1 1 auto !important;
        min-height: 0 !important;
        max-height: none !important;
        overflow-y: auto !important;
        overflow-x: hidden !important;
        gap: 16px !important;
        padding:
            18px
            14px
            calc(34px + env(safe-area-inset-bottom)) !important;
        border-radius: 0 !important;
    }

    .settings-profile {
        flex: 0 0 auto;
        padding: 10px 12px 16px !important;
    }

    .settings-section {
        flex: 0 0 auto;
        overflow: hidden !important;
        padding: 0 !important;
        border-radius: 16px !important;
    }

    .settings-section-title {
        padding: 12px 14px 8px !important;
    }

    .settings-section .settings-push-status {
        padding: 10px 14px 14px 42px !important;
    }

    .settings-section .settings-push-status::before {
        left: 15px !important;
        top: 11px !important;
    }

    .settings-section .push-devices {
        max-height: none !important;
        overflow: visible !important;
    }

    .settings-section .push-device-item {
        grid-template-columns: 36px minmax(0, 1fr) auto !important;
        min-height: 64px;
        padding: 10px 14px !important;
    }

    .settings-section .push-actions {
        padding: 6px 10px 10px !important;
    }

    .settings-logout-button {
        min-height: 52px !important;
    }
}


/* v58.71: unified settings sections and notification card parity. */
.settings-section {
    padding: 0 !important;
}

/* One outer frame per section, without internal cell dividers. */
.settings-section .settings-row-button,
.settings-section .settings-row-button:first-child,
.settings-section .settings-row-button + .settings-row-button {
    border-top: 0 !important;
    border-bottom: 0 !important;
}

.settings-section .settings-row-button + .settings-row-button {
    margin-top: 2px;
}

/* Notification section repeats the approved bell popover design. */
.settings-section .settings-push-status {
    margin: 0 !important;
    padding: 4px 18px 18px 48px !important;
    border: 0 !important;
}

.settings-section .settings-push-status::before {
    left: 19px !important;
    top: 4px !important;
}

.settings-section .push-devices {
    margin: 0 12px 10px !important;
    padding: 0 !important;
    max-height: none !important;
    border: 0 !important;
    border-radius: 14px !important;
    background: rgba(var(--surface-rgb), .58) !important;
    overflow: hidden !important;
}

.settings-section .push-devices-title {
    padding: 13px 14px 7px !important;
}

.settings-section .push-device-item,
.settings-section .push-device-item:first-of-type,
.settings-section .push-device-current {
    padding: 10px 14px 13px !important;
    border: 0 !important;
    border-radius: 0 !important;
    background: transparent !important;
}

.settings-section .push-actions {
    gap: 8px !important;
    margin: 0 12px 12px !important;
    padding: 0 !important;
    border: 0 !important;
}

.settings-section .push-action-button {
    display: grid !important;
    grid-template-columns: 22px minmax(0, 1fr) !important;
    align-items: center !important;
    gap: 10px !important;
    min-height: 48px !important;
    padding: 0 14px !important;
    border: 0 !important;
    border-radius: 13px !important;
    background: rgba(var(--surface-rgb), .58) !important;
}

.settings-section .push-action-button.hidden {
    display: none !important;
}

.settings-section .push-action-button.push-action-danger {
    background: rgba(var(--danger-rgb), .07) !important;
}


/* v58.73: group notification state inside the overflow menu. */
.conversation-overflow-notification-slash {
    opacity: 0;
    transition: opacity 160ms ease;
}

#overflowNotificationsAction.is-muted .conversation-overflow-notification-slash {
    opacity: 1;
}


/* v58.75: one visual language for notification and settings controls. */
.sidebar-header-actions {
    gap: 8px;
}

.push-controls {
    margin: 0 !important;
}

.push-bell-button,
.sidebar-settings-button {
    /*
     * v60.19: у этой пары кнопок (колокольчик уведомлений и шестерёнка
     * настроек) нигде в файле не было touch-action: manipulation - в
     * отличие почти от всех остальных интерактивных элементов
     * приложения (composer, меню сообщения, авторизация и т.п.), где
     * оно явно прописано. Без него браузер на телефоне иногда тратит
     * время на различение жеста (скролл/зум/тап) прежде чем послать
     * click, и в части случаев вовсе его не отправляет - палец касания
     * (:active) уже сработал визуально, а сам клик - нет. Это
     * объясняет жалобу "иногда не срабатывает, без закономерности" по
     * обеим кнопкам сразу (обе используют этот общий блок стилей).
     */
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px !important;
    height: 40px !important;
    flex: 0 0 40px;
    padding: 0;
    border: 1px solid rgba(var(--border-tint-rgb), 0.24);
    border-radius: 13px !important;
    background: linear-gradient(
        180deg,
        rgba(var(--surface-rgb), 0.98),
        rgba(var(--surface-rgb), 0.94)
    );
    color: var(--muted-strong);
    box-shadow:
        0 1px 2px rgba(var(--shadow-rgb), 0.06),
        0 6px 18px rgba(var(--shadow-rgb), 0.08);
    transition:
        transform 140ms ease,
        background 140ms ease,
        border-color 140ms ease,
        box-shadow 140ms ease,
        color 140ms ease;
    touch-action: manipulation;
    -webkit-tap-highlight-color: transparent;
}

.push-bell-button:hover,
.sidebar-settings-button:hover {
    border-color: rgba(var(--accent-light-rgb), 0.26);
    background: var(--panel);
    color: var(--accent);
    box-shadow:
        0 2px 4px rgba(var(--shadow-rgb), 0.07),
        0 10px 24px rgba(var(--accent-rgb), 0.12);
}

.push-bell-button:active,
.sidebar-settings-button:active {
    transform: scale(0.95);
    background: rgba(var(--accent-soft-rgb), 0.98);
}

.push-bell-button:focus-visible,
.sidebar-settings-button:focus-visible {
    outline: 2px solid rgba(var(--accent-rgb), 0.48);
    outline-offset: 2px;
}

.push-bell-icon,
.sidebar-settings-button svg {
    width: 20px !important;
    height: 20px !important;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.push-bell-dot {
    top: 5px;
    right: 5px;
    box-sizing: content-box;
    pointer-events: none;
}

/* v58.78: Telegram-like shared left guide for filters and avatars.
 * On phones both the first filter and every chat avatar begin 20px from
 * the visible left edge. The chat title, timestamp, separators, active
 * row background, avatar size, and hit target keep their existing layout. */
@media (max-width: 780px) {
    .chat-folder-tabs {
        padding-left: max(20px, env(safe-area-inset-left));
    }

    .chat-item > .avatar {
        transform: translateX(-4px);
    }
}


/* v58.79: Bitrix message attachments. */
.message-attachments {
    display: grid;
    gap: 6px;
    margin-bottom: 6px;
}

.message-attachment {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
    color: inherit;
    text-decoration: none;
}

.message-attachment-image {
    display: block;
    width: min(320px, 70vw);
    max-height: 320px;
    object-fit: cover;
    border-radius: 12px;
}

.message-attachment-icon {
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    flex: 0 0 38px;
    border-radius: 10px;
    background: rgba(127, 127, 127, 0.14);
    font-size: 19px;
}

.message-attachment-details {
    display: grid;
    min-width: 0;
}

.message-attachment-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 600;
}

.message-attachment-size {
    opacity: 0.66;
    font-size: 12px;
}


/* v58.82: attachment viewer, lazy large files and clean message cards. */
.message-attachment {
    position: relative;
    border-radius: 12px;
}

.message-attachment.is-clickable {
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.message-attachment.is-clickable:focus-visible {
    outline: 2px solid rgba(var(--accent-rgb), 0.5);
    outline-offset: 2px;
}


.message-attachment.is-large-attachment {
    min-width: min(280px, 68vw);
    padding: 8px 10px;
    background: rgba(127, 127, 127, 0.09);
}

.message-attachment.is-large-attachment .message-attachment-size {
    white-space: normal;
}

.message-attachment-icon.is-pdf,
.message-attachment-icon.is-word,
.message-attachment-icon.is-excel {
    font-weight: 800;
    font-size: 14px;
}

body.attachment-viewer-open {
    overflow: hidden;
}

.attachment-viewer {
    position: fixed;
    inset: 0;
    z-index: 12000;
    display: grid;
    grid-template-rows: auto minmax(0, 1fr);
    background: rgba(12, 14, 18, 0.96);
    color: #fff;
}

.attachment-viewer-header {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: 10px;
    min-height: 56px;
    padding:
        max(8px, env(safe-area-inset-top))
        max(10px, env(safe-area-inset-right))
        8px
        max(10px, env(safe-area-inset-left));
    background: rgba(20, 22, 27, 0.92);
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
}

.attachment-viewer-title {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    text-align: center;
    font-weight: 650;
}

.attachment-viewer-button {
    min-width: 42px;
    height: 40px;
    border: 0;
    border-radius: 12px;
    background: rgba(255,255,255,0.1);
    color: #fff;
    font: inherit;
    cursor: pointer;
}

.attachment-viewer-back {
    font-size: 34px;
    line-height: 1;
}

.attachment-viewer-save {
    padding: 0 12px;
    font-size: 14px;
}

.attachment-viewer-content {
    min-height: 0;
    display: grid;
    place-items: center;
    overflow: auto;
    overscroll-behavior: contain;
    padding: 12px;
}

.attachment-viewer-content.is-pdf {
    padding: 0;
    background: #fff;
}

.attachment-viewer-image {
    display: block;
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    touch-action: pinch-zoom;
    user-select: none;
    -webkit-user-select: none;
}

.attachment-viewer-frame {
    width: 100%;
    height: 100%;
    border: 0;
    background: #fff;
}

.attachment-viewer-file-card {
    width: min(420px, calc(100vw - 32px));
    box-sizing: border-box;
    display: grid;
    justify-items: center;
    gap: 12px;
    padding: 28px 22px;
    border-radius: 22px;
    background: rgba(255,255,255,0.1);
    text-align: center;
}

.attachment-viewer-file-icon {
    display: grid;
    place-items: center;
    width: 76px;
    height: 76px;
    border-radius: 20px;
    background: rgba(255,255,255,0.13);
    font-size: 28px;
    font-weight: 850;
}

.attachment-viewer-file-card.is-word .attachment-viewer-file-icon,
.attachment-viewer-file-card.is-excel .attachment-viewer-file-icon,
.attachment-viewer-file-card.is-pdf .attachment-viewer-file-icon {
    font-size: 30px;
}

.attachment-viewer-file-name {
    max-width: 100%;
    overflow-wrap: anywhere;
    font-weight: 700;
}

.attachment-viewer-file-meta {
    opacity: 0.7;
    font-size: 13px;
}

.attachment-viewer-file-open {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 42px;
    padding: 0 16px;
    border-radius: 12px;
    background: rgba(255,255,255,0.14);
    color: #fff;
    text-decoration: none;
    font: inherit;
    font-weight: 650;
    border: 0;
    cursor: pointer;
}


/* v58.83: audio attachment viewer */
.attachment-viewer-content.is-audio {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
}

.attachment-viewer-audio-card {
    width: min(100%, 520px);
    padding: 22px;
    border-radius: 20px;
    background: rgba(255, 255, 255, 0.96);
    box-shadow: 0 18px 60px rgba(0, 0, 0, 0.22);
}

.attachment-viewer-audio {
    display: block;
    width: 100%;
    margin-top: 16px;
}

.message-attachment-icon.is-audio {
    font-size: 24px;
}


/* iChat Attachments V2 v59.00 */
.message-attachments-v2{display:grid;gap:8px;margin:2px 0 6px}.message-attachment-v2{min-width:220px;max-width:min(72vw,420px);padding:10px;border:1px solid var(--border);border-radius:14px;background:rgba(var(--ink-tint-rgb),.035);overflow:hidden}.attachment-v2-main{display:flex;align-items:center;gap:10px}.attachment-v2-visual{display:grid;place-items:center;flex:0 0 42px;width:42px;height:42px;border-radius:12px;background:var(--panel-alt);font-weight:800;font-size:13px}.attachment-v2-info{min-width:0;flex:1}.attachment-v2-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:650}.attachment-v2-meta{margin-top:3px;color:var(--muted);font-size:12px}.attachment-v2-action{width:100%;margin-top:9px;padding:9px 12px;border:0;border-radius:10px;background:var(--panel-alt);color:var(--accent);font-weight:700;cursor:pointer}.attachment-v2-action:disabled{opacity:.65}.attachment-v2-image,.attachment-v2-video{display:block;width:100%;max-height:360px;object-fit:contain;border-radius:10px;margin-bottom:9px;background:#000}.attachment-v2-audio{width:100%;margin-top:10px}.attachment-queue{grid-column:1/-1;display:grid;gap:6px;padding:8px 8px 0}.composer-file{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:12px;background:var(--panel-alt)}.composer-file-info{min-width:0;flex:1}.composer-file-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:650}.composer-file-meta{margin-top:2px;color:var(--muted);font-size:11px}.composer-file-remove,.attachment-button{border:0;background:transparent;color:var(--muted);cursor:pointer}/* v60.15: у .composer-file-remove раньше не было width/height вообще - тап-зона держалась только на размере символа "x"; теперь явные 36px (компактно, но не как раньше) */.composer-file-remove{display:grid;place-items:center;width:36px;height:36px;font-size:22px;line-height:1}.attachment-input{display:none}/* v60.15: 38px -> 44px - единый iOS tap-target-минимум для круглых icon-button */.attachment-button{align-self:end;width:44px;height:44px;border-radius:999px;font-size:25px}.attachment-button:active{background:var(--panel-alt)}
.composer{flex-wrap:wrap}.attachment-queue{order:-5;flex:0 0 100%;width:100%}.composer-context,.mention-picker{flex-basis:100%}


/* iChat Attachments V2 v59.02: unified document actions */
.attachment-viewer-actions{width:100%;margin-top:20px;display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:6px}
.attachment-viewer-actions-divider{grid-column:1/-1;height:1px;background:var(--border);margin-bottom:8px}
.attachment-viewer-action{min-width:0;border:0;border-radius:12px;background:var(--panel-alt);color:var(--text);padding:10px 4px 8px;display:grid;justify-items:center;gap:5px;font-size:11px;font-weight:650;cursor:pointer}
.attachment-viewer-action:active{transform:scale(.97)}
.attachment-viewer-action:disabled{opacity:.55}
.attachment-viewer-action-icon{font-size:19px;line-height:1;color:var(--accent)}
.attachment-viewer-actions.is-floating{max-width:min(560px,calc(100vw - 32px));margin:18px auto 24px;padding:0 8px}
@media (max-width:390px){.attachment-viewer-actions{gap:4px}.attachment-viewer-action{font-size:10px;padding-inline:2px}}


/* iChat v59.03: subtle dialog wallpaper and compact attachment interaction. */
.message-area {
    position: relative;
    isolation: isolate;
    background-color: #f4f8fa;
    background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22180%22%20height%3D%22180%22%20viewBox%3D%220%200%20180%20180%22%20fill%3D%22none%22%20stroke%3D%22%235f8192%22%20stroke-width%3D%221.35%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20opacity%3D%220.10%22%3E%3Cpath%20d%3D%22M18%2024h26a7%207%200%200%201%207%207v13a7%207%200%200%201-7%207H31l-8%207v-7h-5a7%207%200%200%201-7-7V31a7%207%200%200%201%207-7Z%22%2F%3E%3Cpath%20d%3D%22M105%2019h24a6%206%200%200%201%206%206v28a6%206%200%200%201-6%206h-24a6%206%200%200%201-6-6V25a6%206%200%200%201%206-6Z%22%2F%3E%3Cpath%20d%3D%22m104%2048%208-9%206%206%205-6%208%209%22%2F%3E%3Ccircle%20cx%3D%22115%22%20cy%3D%2230%22%20r%3D%222.5%22%2F%3E%3Ccircle%20cx%3D%2248%22%20cy%3D%22120%22%20r%3D%2218%22%2F%3E%3Cpath%20d%3D%22M48%20109v12l8%205%22%2F%3E%3Cpath%20d%3D%22M117%20103c7-8%2019%203%2012%2010l-13%2013c-5%205-13-2-8-7l12-12c3-3%207%201%204%204l-10%2010%22%2F%3E%3Cpath%20d%3D%22M157%2082h-20a6%206%200%200%200-6%206v10a6%206%200%200%200%206%206h10l7%206v-6h3a6%206%200%200%200%206-6V88a6%206%200%200%200-6-6Z%22%2F%3E%3Cpath%20d%3D%22M18%2078h28M18%2084h20M18%2090h25%22%2F%3E%3Ccircle%20cx%3D%2284%22%20cy%3D%2282%22%20r%3D%224%22%2F%3E%3Cpath%20d%3D%22M84%2069v-5M84%20100v-5M71%2082h-5M102%2082h-5%22%2F%3E%3C%2Fsvg%3E");
    background-size: 180px 180px;
    background-repeat: repeat;
}

@media (prefers-color-scheme: dark) {
    .message-area {
        background-color: #10151b;
        background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22180%22%20height%3D%22180%22%20viewBox%3D%220%200%20180%20180%22%20fill%3D%22none%22%20stroke%3D%22%235f8192%22%20stroke-width%3D%221.35%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20opacity%3D%220.10%22%3E%3Cpath%20d%3D%22M18%2024h26a7%207%200%200%201%207%207v13a7%207%200%200%201-7%207H31l-8%207v-7h-5a7%207%200%200%201-7-7V31a7%207%200%200%201%207-7Z%22%2F%3E%3Cpath%20d%3D%22M105%2019h24a6%206%200%200%201%206%206v28a6%206%200%200%201-6%206h-24a6%206%200%200%201-6-6V25a6%206%200%200%201%206-6Z%22%2F%3E%3Cpath%20d%3D%22m104%2048%208-9%206%206%205-6%208%209%22%2F%3E%3Ccircle%20cx%3D%22115%22%20cy%3D%2230%22%20r%3D%222.5%22%2F%3E%3Ccircle%20cx%3D%2248%22%20cy%3D%22120%22%20r%3D%2218%22%2F%3E%3Cpath%20d%3D%22M48%20109v12l8%205%22%2F%3E%3Cpath%20d%3D%22M117%20103c7-8%2019%203%2012%2010l-13%2013c-5%205-13-2-8-7l12-12c3-3%207%201%204%204l-10%2010%22%2F%3E%3Cpath%20d%3D%22M157%2082h-20a6%206%200%200%200-6%206v10a6%206%200%200%200%206%206h10l7%206v-6h3a6%206%200%200%200%206-6V88a6%206%200%200%200-6-6Z%22%2F%3E%3Cpath%20d%3D%22M18%2078h28M18%2084h20M18%2090h25%22%2F%3E%3Ccircle%20cx%3D%2284%22%20cy%3D%2282%22%20r%3D%224%22%2F%3E%3Cpath%20d%3D%22M84%2069v-5M84%20100v-5M71%2082h-5M102%2082h-5%22%2F%3E%3C%2Fsvg%3E");
        background-blend-mode: screen;
        opacity: 1;
    }
}

.message-attachment-v2 {
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}
.message-attachment-v2:active { transform: scale(0.992); }
.attachment-v2-status {
    margin-top: 8px;
    font-size: 12px;
    opacity: .66;
}
.attachment-viewer-actions { display: none !important; }

/* Attachment frames v60.01
 *
 * Deliberately quiet:
 * - no glass blur;
 * - no large shadow;
 * - no extra bubble around the whole message;
 * - frame follows only the attachment card or image.
 */

.message-bubble :is(
    .attachment-card,
    .attachment-file,
    .attachment-item,
    .attachment-document,
    .file-card,
    .file-item,
    .message-file,
    .message-attachment
),
.message-bubble > [class*="attachment"][class*="card"],
.message-bubble > [class*="attachment"][class*="file"],
.message-bubble [class*="attachments"] > [class*="attachment"] {
    position: relative;
    overflow: hidden;
    box-sizing: border-box;
    border: 1px solid rgba(15, 23, 42, 0.09);
    border-radius: 13px;
    background: rgba(255, 255, 255, 0.18);
    box-shadow: none;
    transition:
        border-color 140ms ease,
        background-color 140ms ease,
        transform 140ms ease;
}

.message-row.mine .message-bubble :is(
    .attachment-card,
    .attachment-file,
    .attachment-item,
    .attachment-document,
    .file-card,
    .file-item,
    .message-file,
    .message-attachment
),
.message-row.mine .message-bubble > [class*="attachment"][class*="card"],
.message-row.mine .message-bubble > [class*="attachment"][class*="file"],
.message-row.mine .message-bubble [class*="attachments"] > [class*="attachment"] {
    border-color: rgba(20, 93, 160, 0.12);
    background: rgba(255, 255, 255, 0.16);
}

.message-bubble :is(
    .attachment-card,
    .attachment-file,
    .attachment-item,
    .attachment-document,
    .file-card,
    .file-item,
    .message-file,
    .message-attachment
):active,
.message-bubble > [class*="attachment"][class*="card"]:active,
.message-bubble > [class*="attachment"][class*="file"]:active {
    transform: scale(0.995);
    border-color: rgba(15, 23, 42, 0.15);
    background: rgba(255, 255, 255, 0.25);
}

/* Compact icon tile. */
.message-bubble :is(
    .attachment-icon,
    .attachment-file-icon,
    .file-icon,
    .message-file-icon
),
.message-bubble [class*="attachment"] > [class*="icon"],
.message-bubble [class*="file-card"] > [class*="icon"] {
    box-sizing: border-box;
    border: 1px solid rgba(15, 23, 42, 0.07);
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.34);
    box-shadow: none;
}

/* Typography stays crisp, not oversized. */
.message-bubble :is(
    .attachment-name,
    .attachment-file-name,
    .file-name,
    .message-file-name
) {
    font-weight: 600;
    letter-spacing: -0.01em;
}

.message-bubble :is(
    .attachment-meta,
    .attachment-file-meta,
    .file-meta,
    .message-file-meta
) {
    opacity: 0.62;
}

/* Images get only a hairline frame. */
.message-bubble :is(
    .attachment-image,
    .attachment-preview-image,
    .message-attachment-image,
    .file-preview-image
),
.message-bubble [class*="attachment"] img,
.message-bubble [class*="file-preview"] img {
    box-sizing: border-box;
    border: 1px solid rgba(15, 23, 42, 0.08);
    border-radius: 13px;
    box-shadow: none;
}

/* Avoid a double frame when an image sits inside a framed wrapper. */
.message-bubble :is(
    .attachment-card,
    .attachment-item,
    .message-attachment
) > :is(
    .attachment-image,
    .attachment-preview-image,
    img
) {
    border: 0;
    border-radius: 12px;
}

/* Do not affect the full-screen attachment viewer. */
.attachment-viewer :is(
    [class*="attachment"],
    [class*="file"]
) {
    border-color: initial;
}

/* Attachment minimal safe UI v60.03
 *
 * CSS only. No observers, no DOM rewrites, no opening-dialog freeze.
 */

/* Hide the decorative file/image icon before the filename. */
.message-bubble :is(
    .attachment-icon,
    .attachment-file-icon,
    .file-icon,
    .message-file-icon
),
.message-bubble [class*="attachment"] > [class*="icon"],
.message-bubble [class*="file-card"] > [class*="icon"] {
    display: none !important;
}

/*
 * When a message bubble contains an attachment and no visible text node,
 * remove the outer bubble skin. The attachment remains the only surface.
 */
.message-bubble:has(
    :is(
        .attachment-card,
        .attachment-file,
        .attachment-item,
        .attachment-document,
        .file-card,
        .file-item,
        .message-file,
        .message-attachment,
        [class*="attachment"][class*="card"],
        [class*="attachment"][class*="file"],
        [class*="file-preview"]
    )
):not(:has(.message-text)) {
    padding: 0 !important;
    border: 0 !important;
    border-radius: 0 !important;
    background: transparent !important;
    box-shadow: none !important;
}

.message-bubble:has(
    :is(
        .attachment-card,
        .attachment-file,
        .attachment-item,
        .attachment-document,
        .file-card,
        .file-item,
        .message-file,
        .message-attachment,
        [class*="attachment"][class*="card"],
        [class*="attachment"][class*="file"]
    )
):not(:has(.message-text))::before {
    display: none !important;
}

/* The attachment itself becomes a quiet, modern card. */
.message-bubble :is(
    .attachment-card,
    .attachment-file,
    .attachment-item,
    .attachment-document,
    .file-card,
    .file-item,
    .message-file,
    .message-attachment
),
.message-bubble > [class*="attachment"][class*="card"],
.message-bubble > [class*="attachment"][class*="file"] {
    box-sizing: border-box;
    gap: 0 !important;
    padding: 10px 12px !important;
    border: 1px solid rgba(15, 23, 42, 0.10) !important;
    border-radius: 12px !important;
    background: rgba(255, 255, 255, 0.32) !important;
    box-shadow: none !important;
}

/* Image attachment shell should not look like a second bubble. */
.message-bubble [class*="attachment"]:has(img),
.message-bubble [class*="file-preview"]:has(img) {
    padding: 0 !important;
    overflow: hidden !important;
    background: transparent !important;
}

/* Keep images crisp with one subtle boundary. */
.message-bubble [class*="attachment"] img,
.message-bubble [class*="file-preview"] img {
    display: block;
    width: 100%;
    border: 0 !important;
    border-radius: 11px !important;
    box-shadow: none !important;
}

/* Filename leads, metadata recedes. */
.message-bubble :is(
    .attachment-name,
    .attachment-file-name,
    .file-name,
    .message-file-name
) {
    margin: 0 !important;
    font-size: 15px !important;
    font-weight: 600 !important;
    line-height: 1.25 !important;
    letter-spacing: -0.01em;
}

.message-bubble :is(
    .attachment-meta,
    .attachment-file-meta,
    .file-meta,
    .message-file-meta
) {
    margin-top: 2px !important;
    font-size: 12px !important;
    line-height: 1.2 !important;
    opacity: 0.58 !important;
}

/* Telegram-style image messages v60.04 */

:root {
    /*
     * One stable media width for image messages.
     * It shrinks only when the viewport is narrower.
     */
    --ichat-image-message-width: 288px;
}

/*
 * Image cards do not display document-style information.
 * The image itself is the entire attachment UI.
 */
.message-attachment-v2.is-image-media
.attachment-v2-main,
.message-attachment-v2.is-image-media
.attachment-v2-info,
.message-attachment-v2.is-image-media
.attachment-v2-visual,
.message-attachment-v2.is-image-media
.attachment-v2-name,
.message-attachment-v2.is-image-media
.attachment-v2-meta,
.message-attachment-v2.is-image-media
.attachment-v2-action,
.message-attachment-v2.is-image-media
.attachment-v2-status {
    display: none !important;
}

/* Remove the document-card shell from all image attachments. */
.message-attachment-v2.is-image-media {
    position: relative;
    box-sizing: border-box;
    width: min(
        var(--ichat-image-message-width),
        calc(100vw - 78px)
    ) !important;
    min-width: 0 !important;
    max-width: none !important;
    margin: 0 !important;
    padding: 0 !important;
    overflow: hidden !important;
    border: 0 !important;
    background: transparent !important;
    box-shadow: none !important;
}

/*
 * Landscape:
 * fixed width, image fills it, no surrounding bubble or frame.
 */
.message-attachment-v2.is-landscape-image
.attachment-v2-image {
    display: block;
    width: 100% !important;
    height: auto !important;
    max-height: 360px !important;
    margin: 0 !important;
    object-fit: cover !important;
    border: 0 !important;
    border-radius: 13px !important;
    background: transparent !important;
    box-shadow: none !important;
}

/*
 * Portrait:
 * same fixed message width, but a compact bubble remains.
 * The picture is contained rather than cropped.
 */
.message-attachment-v2.is-portrait-image {
    padding: 4px !important;
    border-radius: 15px !important;
    background: rgba(255, 255, 255, 0.30) !important;
}

.message-attachment-v2.is-portrait-image
.attachment-v2-image {
    display: block;
    width: 100% !important;
    height: auto !important;
    max-height: 430px !important;
    margin: 0 !important;
    object-fit: contain !important;
    border: 0 !important;
    border-radius: 12px !important;
    background: transparent !important;
    box-shadow: none !important;
}

/*
 * File-only landscape image message:
 * remove the normal text bubble completely.
 */
.message-bubble.landscape-image-message {
    position: relative;
    width: min(
        var(--ichat-image-message-width),
        calc(100vw - 78px)
    ) !important;
    max-width: none !important;
    padding: 0 !important;
    overflow: visible !important;
    border: 0 !important;
    border-radius: 0 !important;
    background: transparent !important;
    box-shadow: none !important;
}

.message-bubble.landscape-image-message::before,
.message-bubble.landscape-image-message::after {
    display: none !important;
}

/* Remove empty text space from an image-only message. */
.message-bubble.image-only-message
.message-text:empty {
    display: none !important;
}

/*
 * Telegram-style time/status overlay for landscape image-only messages.
 * A dark translucent pill keeps it readable over any photo.
 * v60.93: bottom 6 -> 11px — у landscape-фото нет белой нижней полосы
 * (в отличие от portrait), и время/лайк были впритык к нижнему краю
 * снимка. Чуть приподняли для воздуха.
 */
.message-bubble.landscape-image-message
.message-meta {
    position: absolute !important;
    right: 7px !important;
    bottom: 11px !important;
    z-index: 3;
    margin: 0 !important;
    padding: 3px 6px !important;
    border-radius: 999px !important;
    color: rgba(255, 255, 255, 0.96) !important;
    background: rgba(0, 0, 0, 0.40) !important;
    line-height: 1 !important;
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.22);
}

/* Keep delivery ticks visible inside the overlay. */
.message-bubble.landscape-image-message
.message-meta * {
    color: inherit !important;
}

/*
 * Portrait file-only image keeps the existing bubble,
 * but removes duplicated image-card borders and padding.
 */
.message-bubble.portrait-image-message {
    width: min(
        var(--ichat-image-message-width),
        calc(100vw - 78px)
    ) !important;
    max-width: none !important;
    padding: 4px !important;
}

@media (max-width: 390px) {
    :root {
        --ichat-image-message-width: 280px;
    }
}

/* Portrait image spacing v60.05 */

/*
 * Tighten portrait media to match Telegram:
 * the bubble hugs the image instead of leaving a broad blue field.
 */
.message-bubble.portrait-image-message {
    width: min(
        var(--ichat-image-message-width),
        calc(100vw - 78px)
    ) !important;
    max-width: none !important;
    padding: 4px 4px 22px !important;
    border-radius: 15px !important;
}

.message-bubble.portrait-image-message
.message-attachment-v2.is-portrait-image {
    width: 100% !important;
    max-width: none !important;
    margin: 0 !important;
    padding: 0 !important;
    border: 0 !important;
    border-radius: 12px !important;
    background: transparent !important;
    box-shadow: none !important;
}

.message-bubble.portrait-image-message
.message-attachment-v2.is-portrait-image
.attachment-v2-image {
    display: block !important;
    width: 100% !important;
    height: auto !important;
    max-height: 430px !important;
    margin: 0 !important;
    object-fit: contain !important;
    border: 0 !important;
    border-radius: 11px !important;
    background: transparent !important;
    box-shadow: none !important;
}

/* Keep time/status close to the lower-right corner. */
.message-bubble.portrait-image-message
.message-meta {
    position: absolute !important;
    right: 8px !important;
    bottom: 5px !important;
    margin: 0 !important;
}

/* Portrait image edge-to-edge v60.06 */

/*
 * Telegram-like portrait image:
 * no visible bubble above or on the sides.
 * Only a compact bottom area remains for time/status.
 */
.message-bubble.portrait-image-message {
    width: min(
        var(--ichat-image-message-width),
        calc(100vw - 78px)
    ) !important;
    max-width: none !important;
    padding: 0 0 21px !important;
    overflow: hidden !important;
    border: 0 !important;
    border-radius: 14px !important;
    background: transparent !important;
    box-shadow: none !important;
}

/* Remove bubble tail and decorative layers for portrait image-only messages. */
.message-bubble.portrait-image-message::before,
.message-bubble.portrait-image-message::after {
    display: none !important;
}

/* The image wrapper fully matches the message width. */
.message-bubble.portrait-image-message
.message-attachment-v2.is-portrait-image {
    width: 100% !important;
    max-width: none !important;
    margin: 0 !important;
    padding: 0 !important;
    overflow: hidden !important;
    border: 0 !important;
    border-radius: 14px 14px 0 0 !important;
    background: transparent !important;
    box-shadow: none !important;
}

/* Image touches the top and side edges. */
.message-bubble.portrait-image-message
.message-attachment-v2.is-portrait-image
.attachment-v2-image {
    display: block !important;
    width: 100% !important;
    height: auto !important;
    max-height: 430px !important;
    margin: 0 !important;
    object-fit: contain !important;
    border: 0 !important;
    border-radius: 14px 14px 0 0 !important;
    background: transparent !important;
    box-shadow: none !important;
}

/*
 * Only the lower strip remains visible.
 * It is created by the message background, not by extra padding.
 */
.message-bubble.portrait-image-message {
    background:
        linear-gradient(
            to bottom,
            transparent calc(100% - 21px),
            rgba(210, 239, 255, 0.96) calc(100% - 21px)
        ) !important;
}

/* Time/status sits in the lower strip, close to the edge. */
.message-bubble.portrait-image-message
.message-meta {
    position: absolute !important;
    right: 7px !important;
    bottom: 4px !important;
    margin: 0 !important;
    padding: 0 !important;
    z-index: 3;
}

/* Adaptive portrait bubbles v60.07 */

:root {
    --ichat-outgoing-image-bubble:
        rgba(210, 239, 255, 0.96);
    --ichat-incoming-image-bubble:
        rgba(255, 255, 255, 0.96);
    --ichat-image-bottom-strip: 22px;
}

/*
 * Shared portrait foundation.
 * The bubble is always solid, so there is never a transparent gap
 * between the image and the lower time/status strip.
 */
.message-bubble.portrait-image-message {
    position: relative !important;
    width: min(
        var(--ichat-image-message-width),
        calc(100vw - 78px)
    ) !important;
    max-width: none !important;
    min-width: 0 !important;
    overflow: hidden !important;
    border: 0 !important;
    border-radius: 14px !important;
    box-shadow: none !important;
    background:
        var(--ichat-outgoing-image-bubble) !important;
}

.message-row:not(.mine)
.message-bubble.portrait-image-message {
    background:
        var(--ichat-incoming-image-bubble) !important;
}

.message-bubble.portrait-image-message::before,
.message-bubble.portrait-image-message::after {
    display: none !important;
}

/*
 * Wide portrait:
 * bubble is barely visible above and at the sides.
 */
.message-bubble.wide-portrait-image-message {
    padding:
        1px
        1px
        var(--ichat-image-bottom-strip)
        !important;
}

.message-bubble.wide-portrait-image-message
.message-attachment-v2.is-wide-portrait-image {
    width: 100% !important;
    max-width: none !important;
    margin: 0 !important;
    padding: 0 !important;
    overflow: hidden !important;
    border: 0 !important;
    border-radius: 13px 13px 0 0 !important;
    background: transparent !important;
    box-shadow: none !important;
}

.message-bubble.wide-portrait-image-message
.message-attachment-v2.is-wide-portrait-image
.attachment-v2-image {
    display: block !important;
    width: 100% !important;
    height: auto !important;
    max-height: 430px !important;
    margin: 0 !important;
    object-fit: contain !important;
    border: 0 !important;
    border-radius: 13px 13px 0 0 !important;
    background: transparent !important;
    box-shadow: none !important;
}

/*
 * Narrow portrait:
 * preserve organic side bubble areas.
 * The image is centered and never stretches to the full width.
 */
.message-bubble.narrow-portrait-image-message {
    padding:
        4px
        14px
        var(--ichat-image-bottom-strip)
        !important;
}

.message-bubble.narrow-portrait-image-message
.message-attachment-v2.is-narrow-portrait-image {
    width: 100% !important;
    max-width: none !important;
    margin: 0 !important;
    padding: 0 !important;
    overflow: visible !important;
    border: 0 !important;
    border-radius: 12px 12px 0 0 !important;
    background: transparent !important;
    box-shadow: none !important;
    text-align: center !important;
}

.message-bubble.narrow-portrait-image-message
.message-attachment-v2.is-narrow-portrait-image
.attachment-v2-image {
    display: block !important;
    width: auto !important;
    max-width: 82% !important;
    height: auto !important;
    max-height: 430px !important;
    margin: 0 auto !important;
    object-fit: contain !important;
    border: 0 !important;
    border-radius: 11px 11px 0 0 !important;
    background: transparent !important;
    box-shadow: none !important;
}

/*
 * The lower bubble strip is always present for time and delivery state.
 */
.message-bubble.portrait-image-message
.message-meta {
    position: absolute !important;
    right: 7px !important;
    bottom: 4px !important;
    z-index: 3 !important;
    margin: 0 !important;
    padding: 0 !important;
    background: transparent !important;
}

/* Group image sender header v60.08 */

/*
 * Only incoming group messages have .group-message and .message-sender.
 * The sender remains the first child of the bubble, exactly as in a
 * regular group message. We only reserve a proper header area for it.
 */
.message-row.group-message.theirs
.message-bubble:has(.message-sender):has(
    .message-attachment-v2.is-image-media
) {
    position: relative !important;
    overflow: hidden !important;
    padding-top: 0 !important;
    border-radius: 14px !important;
}

/*
 * Match the ordinary group-message sender layout:
 * same left alignment, compact top inset and normal line height.
 */
.message-row.group-message.theirs
.message-bubble:has(.message-attachment-v2.is-image-media)
> .message-sender {
    display: block !important;
    box-sizing: border-box !important;
    width: 100% !important;
    margin: 0 !important;
    padding: 7px 12px 4px !important;
    text-align: left !important;
    line-height: 1.2 !important;
    position: relative !important;
    z-index: 4 !important;
}

/*
 * Remove accidental gaps between sender header and image.
 */
.message-row.group-message.theirs
.message-bubble:has(.message-sender)
> .message-sender
+ .message-attachment-v2.is-image-media {
    margin-top: 0 !important;
}

/*
 * Wide portrait in a group:
 * the image is nearly edge-to-edge below the sender header.
 */
.message-row.group-message.theirs
.message-bubble.wide-portrait-image-message:has(.message-sender) {
    padding:
        0
        1px
        var(--ichat-image-bottom-strip)
        !important;
}

.message-row.group-message.theirs
.message-bubble.wide-portrait-image-message:has(.message-sender)
> .message-sender {
    padding-left: 12px !important;
    padding-right: 12px !important;
}

.message-row.group-message.theirs
.message-bubble.wide-portrait-image-message:has(.message-sender)
.message-attachment-v2.is-wide-portrait-image {
    width: 100% !important;
    margin: 0 !important;
    border-radius: 4px 4px 0 0 !important;
}

.message-row.group-message.theirs
.message-bubble.wide-portrait-image-message:has(.message-sender)
.message-attachment-v2.is-wide-portrait-image
.attachment-v2-image {
    border-radius: 4px 4px 0 0 !important;
}

/*
 * Narrow portrait in a group:
 * keep organic side bubble areas, but sender alignment stays identical
 * to an ordinary group message and is not centered with the image.
 */
.message-row.group-message.theirs
.message-bubble.narrow-portrait-image-message:has(.message-sender) {
    padding:
        0
        14px
        var(--ichat-image-bottom-strip)
        !important;
}

.message-row.group-message.theirs
.message-bubble.narrow-portrait-image-message:has(.message-sender)
> .message-sender {
    margin-left: -2px !important;
    margin-right: -2px !important;
    padding-left: 0 !important;
    padding-right: 0 !important;
}

/*
 * Landscape image in a group with sender:
 * restore a small solid header above the otherwise bubbleless media.
 */
.message-row.group-message.theirs
.message-bubble.landscape-image-message:has(.message-sender) {
    overflow: hidden !important;
    padding:
        0
        1px
        22px
        !important;
    border-radius: 14px !important;
    background:
        var(--ichat-incoming-image-bubble, rgba(255, 255, 255, 0.96))
        !important;
}

.message-row.group-message.theirs
.message-bubble.landscape-image-message:has(.message-sender)
> .message-sender {
    padding: 7px 11px 4px !important;
}

.message-row.group-message.theirs
.message-bubble.landscape-image-message:has(.message-sender)
.message-attachment-v2.is-landscape-image,
.message-row.group-message.theirs
.message-bubble.landscape-image-message:has(.message-sender)
.attachment-v2-image {
    border-radius: 4px 4px 0 0 !important;
}

/* Time remains in the always-present lower bubble strip. */
.message-row.group-message.theirs
.message-bubble:has(.message-sender):has(
    .message-attachment-v2.is-image-media
)
.message-meta {
    right: 7px !important;
    bottom: 4px !important;
}

/*
 * v60.91: фото-сообщение (картинка без подписи) с лайком — бейдж 👍
 * уезжает в ЛЕВЫЙ нижний угол снимка, время/галочки остаются справа.
 * Раньше оверлей .message-meta ужимался по контенту у правого края,
 * и лайк липнул вплотную к времени. Только при .has-like — «голое»
 * фото без лайка выглядит как прежде.
 */
.message-bubble.landscape-image-message .message-meta.has-like,
.message-bubble.portrait-image-message .message-meta.has-like {
    left: 7px !important;
    right: 7px !important;
    justify-content: space-between !important;
    gap: 6px !important;
    padding: 0 !important;
    background: transparent !important;
}

.message-bubble.landscape-image-message .message-meta.has-like .message-meta-time {
    padding: 3px 7px;
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.42);
    color: rgba(255, 255, 255, 0.96);
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.22);
}

.message-bubble.landscape-image-message .message-meta.has-like .message-like,
.message-bubble.portrait-image-message .message-meta.has-like .message-like {
    margin: 0 !important;
    min-width: 0 !important;
    height: auto !important;
    padding: 3px 8px !important;
    border: 0 !important;
    border-radius: 999px !important;
    background: rgba(0, 0, 0, 0.42) !important;
    color: rgba(255, 255, 255, 0.96) !important;
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.22);
    line-height: 1 !important;
}

.message-bubble.landscape-image-message .message-meta.has-like .message-like.active,
.message-bubble.portrait-image-message .message-meta.has-like .message-like.active {
    background: rgba(var(--accent-rgb), 0.92) !important;
    color: #fff !important;
}

/* Image bubble width alignment v60.09 */

/*
 * Photo-only bubbles were a few pixels wider than regular messages.
 * Keep one fixed media width, but trim it slightly for visual parity.
 */
:root {
    --ichat-image-message-width: 288px;
}

@media (max-width: 390px) {
    :root {
        --ichat-image-message-width: 280px;
    }
}


/* Real image bubble width fix v60.10
 * All active desktop declarations are 312px.
 * All active <=390px declarations are 298px.
 */


/* Measured photo-only width v60.11
 * Browser measurement before patch:
 * photo-only bubble = 312px
 * nearby normal text bubble = 282.96px
 *
 * New target:
 * regular screens = 288px
 * <=390px = 280px
 */

/* Photo equals text bubble width v60.12 */

/*
 * Ordinary long text bubbles use 78% of the message row.
 * Apply exactly the same responsive width to photo-only bubbles.
 *
 * This intentionally overrides the old fixed
 * --ichat-image-message-width values.
 */
.message-bubble.image-only-message:is(
    .portrait-image-message,
    .landscape-image-message
) {
    box-sizing: border-box !important;
    width: 78% !important;
    max-width: 78% !important;
}

/* The media wrapper must fill the now text-sized bubble exactly. */
.message-bubble.image-only-message
.message-attachment-v2.is-image-media {
    box-sizing: border-box !important;
    width: 100% !important;
    max-width: 100% !important;
}

/*
 * Wide portrait group images use a 1px bubble side inset.
 * Keep that existing Telegram-style inset without changing
 * the outer bubble width.
 */
.message-row.group-message.theirs
.message-bubble.image-only-message.wide-portrait-image-message:has(
    .message-sender
)
.message-attachment-v2.is-wide-portrait-image {
    width: 100% !important;
    max-width: 100% !important;
}
