/* =========================================================
   MURRLINK — УЗКИЙ ЭКРАН (каркас /app/ и приложение целиком)

   Зачем файл. Каркас /app/ — три колонки фиксированной ширины:
   рельс серверов (72), панель каналов (240), основная область
   (растягивается), участники (240) плюс зазоры и отступы body.
   Минимум — 594 px, поэтому на телефоне (360/390 px) правая колонка
   просто уезжала за экран (замер ДО: `scrollWidth` 594 против окна 390,
   семь узлов за правым краем). Ширины колонок — не оформление, а размеры
   каркаса, они лежат в `@layer layout` внутри app.css; править их там
   значило бы менять раскладку и на широком экране.

   Какой подход. Тот же интерфейс, а не отдельная мобильная версия:
   на узкой ширине боковые панели становятся ШТОРКАМИ поверх основной
   области, а в шапке появляются три кнопки (серверы, каналы, участники).
   Основная область (чат, голосовая область, беседа, друзья) занимает всю
   ширину. Ширина шире 768 px не затронута вообще — этим же интерфейсом
   пользуется десктопное приложение в WebView2.

   Слои. Список слоёв объявлен один раз в tokens.css и повторён здесь
   ИМЕННО В ТОМ ЖЕ ПОРЯДКЕ: повторная инструкция `@layer` только добавляет
   файл в уже зафиксированный порядок (правила не переезжают в другой слой)
   и не заводит новых слоёв. Уточнения к правилам каркаса лежат в том же
   слое, что и сами правила (`layout`), уточнения к кнопкам — в
   `components`: чем общее правило, тем раньше слой.

   Цвета — только токенами из tokens.css.
   ========================================================= */
@layer tokens, base, layout, components, features, pages;

/* Кнопки панелей, верхняя полоса и подложка шторок. Вне media-запроса —
   потому что это ДОБАВЛЕНИЕ к каркасу: на широком экране их просто нет
   (`display: none`), и раскладка 1440/1280/1024/800 остаётся прежней. */
@layer layout {
    .panel-toggle {
        display: none;
    }

    .panel-backdrop {
        display: none;
    }

    .mobile-bar {
        display: none;
    }

    /* Шапка слоя — добавление к панелям и окнам, а не их часть: на широком
       экране её нет вовсе (там панели стоят в потоке, а окна закрываются
       крестиком). На узком — это единственная крупная кнопка «Назад»,
       которая закрывает РОВНО текущий слой (static/js/features/
       panel-drawer.js). */
    .layer-head {
        display: none;
    }

    .layer-back {
        display: none;
    }
}

/* ------------------------------------------------------------------
   Видимый фокус для всего, до чего можно добраться с клавиатуры.
   Раньше кольцо было только у части элементов (`#user-status-trigger`,
   `.status-item`, поля ввода, ползунки, флажки, ссылки страниц входа):
   у остальных оставался браузерный `outline`, который на тёмном стекле
   почти не виден. Правило лежит в слое `base` (общее для всех страниц) и
   не отменяет существующие `:focus-visible`: у тех же правил в app.css
   такой же вес, но `box-shadow`-кольцо остаётся на месте, а `outline`
   рисуется ПОВЕРХ него тем же акцентом — вид кольца один и тот же.
   ------------------------------------------------------------------ */
@layer base {
    :focus-visible {
        outline: 2px solid var(--accent);
        outline-offset: 2px;
    }
}

/* ------------------------------------------------------------------
   «Работает только по наведению» — доступный путь.
   В app.css (слой components) панель кнопок и ползунок громкости
   демонстрации показываются ТОЛЬКО при наведении на сцену
   (`.screen-stage:hover .screen-stage-controls`). С клавиатуры до них было
   не добраться: Tab попадал в невидимые кнопки. Добавляем `:focus-within` —
   тот же показ, когда фокус внутри сцены. Правило действует на ЛЮБОЙ
   ширине: на широком экране оно ничего не меняет, пока фокус не в сцене.
   ------------------------------------------------------------------ */
@layer components {
    .screen-stage:focus-within .screen-stage-controls,
    .screen-stage:focus-within .screen-stage-volume {
        opacity: 1;
    }
}

/* ------------------------------------------------------------------
   Телефон и узкий планшет: --bp-md (768px).
   ------------------------------------------------------------------ */
@media (max-width: 768px) {

    @layer layout {

        /* --- Верхняя полоса: единственная кнопка, которая всегда на виду --
           Шторку открывает кнопка, а кнопка внутри закрытой шторки её не
           откроет: без постоянной полосы список каналов недостижим, пока не
           выбран канал (живая проверка выложенной страницы это и показала).
           Полоса лежит в потоке первым элементом каркаса, поэтому «съедает»
           высоту у основной области, а не накрывает её. ---------------- */
        .mobile-bar {
            display: flex;
            align-items: center;
            gap: 8px;
            flex: 0 0 auto;
            height: 56px;
            padding: 0 8px;
            background: var(--bg-panel);
            border-bottom: 1px solid var(--border);
            color: var(--text-primary);
        }
        /* Кнопки панелей живут в верхней полосе — это ЕДИНСТВЕННЫЙ вход в
           панели (см. templates/servers/_panel_toggles.html): квадратный вид и
           цель касания заданы здесь, поэтому правило и называет полосу.
           Кроме полосы на узком экране есть ещё кнопка «Открыть список
           каналов» в пустом состоянии — она с подписью и со своим оформлением
           (ниже), а на широком экране кнопок панелей нет вовсе: полоса скрыта,
           а `.panel-toggle { display: none }` из app.css (слой layout, вне
           media-запроса) гасит в том числе кнопку пустого состояния. */
        .mobile-bar .panel-toggle {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            flex: 0 0 auto;
            /* Цель касания: не меньше 44x44 (требование задачи). */
            width: 44px;
            height: 44px;
            padding: 0;
            background: none;
            border: 1px solid transparent;
            border-radius: var(--radius-md);
            color: var(--text-icon);
            font-size: 18px;
            cursor: pointer;
        }
        .panel-toggle:hover {
            background: var(--bg-hover);
            color: var(--text-normal);
        }
        .panel-toggle[aria-expanded="true"] {
            background: var(--accent-soft);
            border-color: var(--border-strong);
            color: var(--text-primary);
        }

        /* Кнопка полосы спрятана, когда открывать нечего. Атрибут ставит
           static/js/features/panel-drawer.js: syncBarToggles() — по факту
           показа панели (`getComputedStyle`), а не по строке inline-стиля.
           ПОЧЕМУ так, а не правилом `body:has(#panel[style*="none"])`: строка
           стиля зависит от того, как её сериализует браузер, а снятие
           inline-стиля (`style.display = ''`) вообще убирает атрибут — на этом
           уже один раз сломалось открытие шторки. Атрибут `hidden` — один
           источник правды и для CSS, и для решения «можно ли закрыть шторку»,
           поэтому правило стоит сразу после вида кнопок. */
        .mobile-bar .panel-toggle[hidden] {
            display: none;
        }

        /* Кнопка «Открыть список каналов» в пустом состоянии: не квадратная
           иконка, а кнопка с подписью. `display` назван явно и селектором
           конкретнее общего правила выше — иначе вид кнопки решал бы порядок
           правил в этом же media-запросе. */
        .empty-state .panel-toggle {
            display: inline-flex;
            width: auto;
            min-height: 44px;
            margin-top: 8px;
            padding: 0 14px;
            gap: 8px;
            border-color: var(--border-strong);
        }
        .mobile-bar-title {
            font-weight: 600;
            font-size: 15px;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        /* --- Основная область — на всю ширину, во всю высоту ----------
           ПОЧЕМУ здесь меняется направление главной оси. Каркас на широком
           экране — ЧЕТЫРЕ колонки в ряд (`body { display: flex }` в app.css,
           слой layout), и верхней полосы среди них нет: она спрятана
           (`display: none`). На узком экране полоса появляется — и если ось
           остаётся горизонтальной, полоса становится ПЯТОЙ КОЛОНКОЙ: она
           занимает ширину по содержимому (замер 500 px: 145 px), а основная
           область начинается не с нуля, а от её правого края (замер: `left`
           145, ширина 355 вместо 500). Это и было на снимке владельца: полоса
           сверху, а область сдвинута вправо и зажата в 355 px.
           Полоса лежит в потоке ПЕРВЫМ элементом каркаса, поэтому колонка —
           это ровно «полоса сверху, область под ней на всю ширину и на всю
           оставшуюся высоту»: `flex: 1` у `.main-area` из app.css сам занимает
           остаток (замер 500x1126 — полоса 500x56, область 500x1070 под ней,
           полоса управления голосом внутри области, низ не обрезан).

           ПОЧЕМУ селектор называет полосу, а не просто `body`. `body` есть и
           на девяти ДРУГИХ страницах, и там это центрирующая обёртка
           (`auth.css`: `display: flex; align-items: center; justify-content:
           center`): смена оси переставила бы карточку входа из центра вверх.
           Это поймал слепок `tools/mobile_audit.py`: правило `body` дало
           отличия на login/register/profile/settings/styleguide/home/chat/
           voice. Полоса `.mobile-bar` есть ТОЛЬКО в каркасе `/app/`
           (templates/servers/app.html), поэтому `:has(> .mobile-bar)` — это
           ровно «каркас приложения», и ни одна другая страница правило не
           видит. Здесь же он и нужен: на широком экране полосы нет вовсе
           (`display: none`), значит признак честно описывает узкий каркас.
           Правило живёт только в этом media-запросе: шире 768 px направление
           оси прежнее, и раскладка 1440 остаётся ровно такой, как была
           (слепок getComputedStyle: 0 отличий на 1440/1280/1024/800). ----- */
        body:has(> .mobile-bar) {
            flex-direction: column;
            gap: 0;
            padding: 0;
        }
        .main-area {
            width: 100%;
            height: 100%;
            border: none;
            border-radius: 0;
        }

        /* --- Панели каркаса: шторки поверх основной области -----------
           Рельс серверов и панель каналов выезжают слева, участники —
           справа. Панели начинаются ПОД верхней полосой: кнопка, которая их
           закрывает, остаётся видимой и доступной. `z-index` подобран под
           существующую шкалу app.css: меню канала и карточка пользователя
           (9998/9999) и модальные окна (10000) остаются ВЫШЕ шторок, поэтому
           всплывающие окна по-прежнему перекрывают панель, а не наоборот. */
        .servers-rail,
        .channels-panel,
        .members-panel {
            position: fixed;
            top: 56px;
            bottom: 0;
            height: auto;
            max-width: 100vw;
            z-index: 40;
            /* Панель поверх области — тень отделяет её от содержимого. */
            box-shadow: var(--panel-shadow), var(--inset-highlight);
        }
        .servers-rail {
            left: 0;
            width: 72px;
            transform: translateX(-110%);
            visibility: hidden;
            transition: transform var(--transition-base),
                        visibility var(--transition-base);
        }
        .channels-panel {
            left: 0;
            /* Ширина — доля экрана, но не шире прежних 240 px: на телефоне
               список каналов не должен занимать весь экран. */
            width: min(280px, 78vw);
            transform: translateX(-110%);
            visibility: hidden;
            transition: transform var(--transition-base),
                        visibility var(--transition-base);
        }
        .servers-rail.drawer-open {
            transform: none;
            visibility: visible;
        }
        .channels-panel.drawer-open {
            transform: none;
            visibility: visible;
        }

        /* Участники — справа. Своё состояние (`drawer-open`), потому что
           открыть их можно и при закрытых каналах. */
        aside.members-panel {
            right: 0;
            width: min(280px, 78vw);
            transform: translateX(110%);
            visibility: hidden;
            transition: transform var(--transition-base),
                        visibility var(--transition-base);
        }
        aside.members-panel.drawer-open {
            transform: none;
            visibility: visible;
        }

        /* Панель переписок (#dm-panel) — тот же .channels-panel, но у неё две
           особенности, и обе важны для возврата к списку переписок на узком
           экране.
           1. Прячет её класс .is-hidden (dm.css, слой components; он ВЫШЕ слоя
              layout) — отсюда `body:has(...)`, который поднимает вес правила
              выше `.dm-panel.is-hidden`, сохраняя саму логику: в режиме личных
              сообщений одна панель видна, вторая скрыта.
           2. Выехать ей мешает общее правило шторки `.channels-panel.drawer-open`
              (выше в этом блоке): у панели переписок тот же класс, и он держит
              `transform: translateX(-110%)` и `visibility: hidden`. Вес
              `:has()`-селектора равен весу его аргумента — те же (0,2,0), а
              выигрывает позднее правило, то есть общее. Поэтому панель
              переписок названа с id: `#dm-panel.drawer-open`.
           Раньше здесь стоял `body:has(.dm-panel.drawer-open) > .dm-panel`:
           дочерний комбинатор не совпадал вообще (панель ЛС лежит в каркасе не
           первым уровнем), а `.dm-mode` в body не ставит никто — то есть в
           режиме ЛС шторка переписок не открывалась ни при каких условиях, и
           вернуться из беседы к списку было нечем (владелец).
           ★ Оба мёртвых селектора удалены — они не совпадали никогда (про это
           и написано выше), но их `:has()` продолжал участвовать в
           инвалидации совпадений: движок перепроверял `body` при каждой
           мутации поддерева. Поведение держит один `#dm-panel.drawer-open`. */
        #dm-panel.drawer-open {
            transform: none;
            visibility: visible;
        }

        /* --- Шапка слоя: крупная «Назад» + название -----------------
           Правило «один слой за раз» (features/panel-drawer.js) требует,
           чтобы у каждого слоя была СВОЯ кнопка закрытия: иначе на 390
           «назад» угадывает, какой слой закрыть. Кнопка — 44x44 (цель
           касания), стоит первой в шапке. На широком экране шапок нет
           (`display: none` вне media, выше в файле). ---------------- */
        .layer-head {
            display: flex;
            align-items: center;
            gap: 8px;
            flex: 0 0 auto;
            min-height: 52px;
            padding: 4px 8px;
            background: var(--bg-panel);
            border-bottom: 1px solid var(--border);
        }
        .layer-head--rail {
            justify-content: center;
            padding: 4px 0;
        }
        /* Окна (карточка, профиль) уже имеют свой фон и отступы: шапка
           вписывается в них без второй плашки. */
        .layer-head--flush {
            min-height: 44px;
            padding: 0;
            margin-bottom: 4px;
            background: transparent;
            border-bottom: none;
        }
        /* Окно профиля на коротком экране (390x640) и так упирается в край:
           ещё 44 px шапки в потоке выдавливали кнопки редактора аватара под
           подсказку (поймал tools/check_mobile_layout.py). Поэтому здесь
           шапка не занимает высоту: кнопка «Назад» лежит в отступе окна, а
           заголовок формы получает отступ слева, чтобы не наезжать на неё. */
        #profile-modal .modal-content > .layer-head--flush {
            position: absolute;
            top: 4px;
            left: 4px;
            z-index: 2;
            min-height: 0;
            height: 44px;
            margin: 0;
            padding: 0;
        }
        #profile-modal .layer-head-title {
            display: none;
        }
        #profile-modal .profile-modal-form h2 {
            padding-left: 52px;
        }
        .layer-head-title {
            min-width: 0;
            font-size: 13px;
            font-weight: 600;
            color: var(--text-muted);
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        /* Шапка под шторки: не ниже цели касания и с воздухом по краям,
           чтобы кнопки не липли к рамке экрана. */
        .main-header,
        .server-header {
            min-height: 56px;
            padding-left: 8px;
            padding-right: 8px;
            gap: 4px;
        }
        /* Порядок шапки сервера: НАЗВАНИЕ первым, на всю строку, а кнопки —
           под ним. Почему так, а не «всё в одну строку»: в шапке каналов
           пять органов управления — три кнопки панелей (44 px), шестерёнка
           управления сервером и «Пригласить». Замер на 390 px при ширине
           панели 280 px: в одну строку они не влезают, название сжималось до
           22 px («T…»), а шестерёнка и «Пригласить» уезжали на вторую строку
           и оставались невидимыми. С переносом названия наверх влезает всё,
           и остаётся воздух. Заголовки канала и беседы — тоже на своей строке
           (иначе имя канала ужималось до «g…»). */
        /* ★ Аватар и статус собеседника в шапке ЛС — НЕ заголовок: раньше
           селектор растягивал каждый span на всю строку, и на 390 аватар
           превращался в фиолетовый овал во всю ширину. Заголовок («Беседа» /
           имя собеседника) остаётся на своей строке, а слоты аватара и
           статуса — обычные элементы строки. */
        .server-header .name,
        .main-header > span:not(.voice-header-meta):not(.hash):not(.dm-peer-avatar):not(.dm-peer-status) {
            order: -1;
            flex: 1 1 100%;
            min-width: 0;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
        /* Высоту шапки каналов снимаем: она была жёстко 48 px из app.css,
           а теперь строк две. */
        .server-header {
            height: auto;
            flex-wrap: wrap;
        }
        /* Кнопки шапки сервера — тоже цели касания. */
        .server-header .invite-btn,
        .server-header .tv-manage-btn {
            min-width: 44px;
            min-height: 44px;
        }

        /* --- Подложка шторки -----------------------------------------
           Пока открыта панель КАНАЛОВ или УЧАСТНИКОВ, область вокруг
           затемнена и нажатие по ней закрывает панель (обработчик —
           static/js/features/panel-drawer.js). Рельс серверов подложки не
           заводит: он тонкий, и основная область остаётся рабочей.

           Панель лежит ВЫШЕ подложки, поэтому содержимое шторки доступно
           для нажатия. Нажатие по самой области ловит `#main-area`. ---- */
        .panel-backdrop {
            display: block;
            position: fixed;
            inset: 0;
            z-index: 30;
            padding: 0;
            background: var(--overlay-soft);
            border: none;
            cursor: pointer;
            opacity: 0;
            pointer-events: none;
            transition: opacity var(--transition-base);
        }
        body:has(.channels-panel.drawer-open) .panel-backdrop,
        body:has(.members-panel.drawer-open) .panel-backdrop {
            opacity: 1;
            pointer-events: auto;
        }

        /* --- Цели касания: не меньше 44x44 --------------------------- */
        .icon-btn,
        .user-actions .icon-btn,
        .user-avatar-link,
        .user-avatar,
        .invite-btn {
            min-width: 44px;
            min-height: 44px;
        }
        .user-avatar {
            width: 44px;
            height: 44px;
        }
        .user-info {
            min-height: 44px;
            display: flex;
            flex-direction: column;
            justify-content: center;
        }
        .user-panel {
            gap: 4px;
            padding: 8px;
        }
        .members-search {
            min-height: 44px;
        }
        /* Пункт меню статуса и строка участника: строка списка — тоже цель
           касания, а на палец 32 px мало. */
        .status-item {
            min-height: 44px;
            align-items: center;
        }
        .channel,
        .member {
            min-height: 44px;
        }

        /* --- Голосовая панель ---------------------------------------
           Полоса управления голосом — шесть кнопок в ряд: на 360 px они
           ужимались до 40 px и меньше. Разрешаем перенос и задаём минимум. */
        .voice-controlbar {
            flex-wrap: wrap;
            gap: 8px;
            padding: 8px 12px;
        }
        .voice-controlbar-info {
            min-width: 0;
            flex: 1 1 100%;
        }
        .voice-conn-title,
        .voice-status {
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
        .vqc-btn {
            min-width: 44px;
            min-height: 44px;
        }
        /* Кнопки управления демонстрацией поверх сцены: 32x32 из app.css —
           на палец мелко, а сцена занимает почти весь экран телефона. */
        .screen-stage-btn {
            width: 44px;
            height: 44px;
        }
        /* Подсказка о звуке демонстрации — длинная строка: на узкой ширине
           занимает свою строку и переносится, а не растягивает полосу. */
        .voice-sysaudio {
            flex: 1 1 100%;
            white-space: normal;
            font-size: 12px;
            line-height: 1.35;
        }
        .voice-controlbar-right {
            flex: 1 1 100%;
        }

        /* --- Шкала громкости микрофона -------------------------------
           Минимум 320 px из app.css (слой features) шире телефона: на
           360 px блок вылезал за экран на 34 px. На узкой ширине минимум
           снимаем, а строку с подписью разрешаем переносить. ---------- */
        .mic-volume-wrap {
            min-width: 0;
            width: 100%;
            flex-wrap: wrap;
            gap: 8px;
            padding: 10px 12px;
        }
        .mic-volume-wrap label {
            white-space: normal;
        }
        .mic-volume-wrap input[type="range"] {
            min-width: 120px;
        }

        /* --- Плитки голосовой области --------------------------------
           Голосовая область считает раскладку в JS (voice/grid.js), но
           пустая область и подпись «в канале никого» тоже должны
           переноситься, а не растягивать каркас. --------------------- */
        .voice-empty {
            padding: 16px;
            text-align: center;
            /* Запас 200 px из app.css плюс высокий блок подсказок внизу
               выдавливали полосу управления за нижний край (проверено
               снимком: «Подключиться» уезжала под экран). Пустое состояние
               сжимается — подпись и кнопка остаются на виду. */
            min-height: 0;
        }
        .voice-empty .voice-empty-icon {
            width: 64px;
            height: 64px;
            font-size: 26px;
        }
        .empty-state {
            padding: 16px;
            text-align: center;
        }
        .empty-state h2,
        .empty-state p {
            overflow-wrap: anywhere;
        }

        /* --- Окна и модальные окна ----------------------------------
           Модалка шире экрана не влезала и её нельзя было закрыть: крестик
           уезжал за край. Ограничиваем ширину окна экраном и разрешаем
           прокрутку содержимого. ------------------------------------- */
        .modal-content,
        .settings-modal,
        .share-modal {
            max-width: 100%;
            width: 100%;
        }
        .settings-modal-backdrop {
            padding: 0;
        }
        .settings-modal-body,
        .share-modal-body,
        .tv-manage-body {
            overflow-x: hidden;
        }
        .modal-close,
        .settings-modal-close,
        .user-card-close {
            min-width: 44px;
            min-height: 44px;
        }
    }
}

/* ------------------------------------------------------------------
   «Назад» слоя — на узком экране это ОРГАН УПРАВЛЕНИЯ, а не украшение:
   им закрывают текущий слой, когда системного «назад» под рукой нет.
   Поэтому цель касания 44x44 и видимая рамка; на широком экране кнопки
   нет вовсе (display:none в слое layout выше), и вид 1440 не меняется.
   ------------------------------------------------------------------ */
@media (max-width: 768px) {

    @layer components {

        .layer-back {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            flex: 0 0 auto;
            width: 44px;
            height: 44px;
            min-width: 44px;
            min-height: 44px;
            padding: 0;
            border: 1px solid var(--border);
            border-radius: var(--radius-md);
            background: transparent;
            color: var(--text-primary);
            font-family: inherit;
            font-size: 22px;
            line-height: 1;
            cursor: pointer;
        }
        .layer-back:hover {
            background: var(--btn-icon-hover-bg);
        }
        .layer-back:active {
            background: var(--bg-sidebar-dark);
        }

        /* Действия карточки: «Написать» + «Открыть страницу» + «Скопировать
           ссылку» в одну строку на 390 не влезают — разрешаем перенос. */
        .user-card-actions {
            flex-wrap: wrap;
        }
    }
}

/* ------------------------------------------------------------------
   Очень узкий экран (≤ 360 px): числа и подписи в одну колонку.
   Отдельный порог — --bp-sm (480px) в шкале tokens.css для страниц уже
   занят, а каркасу нужна своя граница: 360 и уже.
   ------------------------------------------------------------------ */
@media (max-width: 380px) {

    @layer layout {

        .voice-controlbar-buttons {
            flex-wrap: wrap;
        }
        .panel-toggle {
            width: 44px;
            height: 44px;
        }
        .main-header,
        .server-header {
            padding-left: 4px;
            padding-right: 4px;
        }
    }
}

/* ------------------------------------------------------------------
   Цели касания на ОСТАЛЬНЫХ страницах (не на каркасе /app/).

   Замер ДО на 390 px: /settings/ — 27 целей меньше 44x44 (переключатели
   16x16, поля 33 px, кнопки 29 px), /profile/ — кнопки 40 px, /user/1/ —
   ссылки 37 px, вход и регистрация — текстовая ссылка 20 px в высоту,
   витрина — крестики 20x22 и кнопки 24–43 px.

   Скоуп — по классу body страницы и по контексту окна: правило не
   «размазано» по всем страницам, а называет, где именно действует.
   Порог — тот же --bp-md (768px), что и у шторок: на широком экране
   размеры не меняются вовсе. Слои: это уточнения к правилам страниц,
   поэтому `pages` (там же, где лежат сами страницы), а для элементов
   витрины и окна настроек — `components` (там их рисует app.css).
   ------------------------------------------------------------------ */
@media (max-width: 768px) {

    @layer components {

        /* --- Окно настроек в приложении (#settings-modal) ------------ */
        #settings-modal .checkbox {
            min-height: 44px;
            align-items: center;
            padding: 4px 0;
        }
        #settings-modal select {
            min-height: 44px;
        }
        #settings-modal button {
            min-height: 44px;
        }
        #settings-modal input[type="range"] {
            min-height: 44px;
        }
        /* Ползунок в окне настроек: сам трек тонкий, но палец должен
           попадать по всей строке. */
        #settings-modal .row {
            gap: 8px;
        }

        /* --- Витрина компонентов: демонстрации должны быть
           работоспособны пальцем. Меняются только размеры целей
           касания, вид компонента не переделывается. ---------------- */
        .sg-section .icon-btn,
        .sg-section .sg-reply-cancel,
        .sg-section .sg-vi-close,
        .sg-section .user-card-close,
        .sg-section .vph-close,
        .sg-section .jump-to-bottom {
            min-width: 44px;
            min-height: 44px;
        }
        .sg-section .user-card-btn,
        .sg-section .context-menu-volume-reset,
        .sg-section .invite-btn,
        .sg-section .voice-btn,
        .sg-section button.btn-pill {
            min-height: 44px;
        }
        .sg-section .members-search,
        .sg-section .form-input,
        .sg-section .context-menu-volume-slider {
            min-height: 44px;
        }
    }

    @layer pages {

        /* --- Профиль и публичный профиль ---------------------------- */
        body.profile-page .form-input,
        body.profile-page .form-group input,
        body.profile-page .form-group textarea,
        body.profile-page input[type="text"],
        body.profile-page input[type="email"],
        body.profile-page textarea,
        body.user-profile-page .form-input {
            min-height: 44px;
        }
        body.profile-page .avatar-upload-btn {
            min-height: 44px;
            display: inline-flex;
            align-items: center;
        }
        body.profile-page .btn-primary,
        body.profile-page .btn-secondary,
        body.profile-page a.btn-secondary,
        body.user-profile-page a {
            min-height: 44px;
            display: inline-flex;
            align-items: center;
        }

        /* --- Вход и регистрация -------------------------------------
           Ссылка «Зарегистрироваться» / «Войти» — текстом 20 px в высоту:
           на палец мимо. Делаем её строкой-целью, вид не меняем. ------ */
        body.login-page a,
        body.register-page a {
            min-height: 44px;
            display: inline-flex;
            align-items: center;
        }
        /* Поля входа/регистрации: 43 px в высоту — не хватало 1 px. */
        body.login-page input,
        body.register-page input {
            min-height: 44px;
        }

        /* --- Домашняя страница ------------------------------------- */
        body.home-page a,
        body.home-page button {
            min-height: 44px;
        }

        /* --- Страница канала и голосовая страница -------------------
           Переносят длинные подписи, чтобы каркас не растягивался. --- */
        body.channel-page .header,
        body.voice-page .header {
            overflow-wrap: anywhere;
        }

        /* --- Ссылки-возвраты в шапках страниц -----------------------
           «← В приложение» и «← На главную» были текстом 19–31 px в высоту:
           на палец мимо. Строка становится целью касания, вид тот же. */
        body.profile-page .back-link,
        body.voice-page .header a,
        body.channel-page .header a,
        body.styleguide-page .sg-header a,
        body.user-profile-page .actions a {
            min-height: 44px;
            display: inline-flex;
            align-items: center;
        }

        /* --- Фокус на узкой ширине: иконки-кнопки 32 px из app.css ----------
           Кнопки «настройки», «выйти», «скачать» и стрелка «вниз» в чате
           были 32 px: палец мимо. ------------------------------------- */
        .icon-btn,
        .user-actions .icon-btn {
            min-width: 44px;
            min-height: 44px;
        }

        /* --- Страница текстового канала: поле ввода -----------------
           Отдельная страница (/chat/<id>/), поле 36 px в высоту. ----- */
        body.channel-page .input-row input {
            min-height: 44px;
        }

        /* --- Ссылка витрины «наверх» и прочие текстовые ссылки ----- */
        body.styleguide-page .sg-header a {
            padding: 0 4px;
        }

        /* --- Голосовая страница: кнопки управления ------------------
           36–39 px в высоту на 390 px. Минимум задаётся честно только при
           `border-box`: у страницы голосового канала свой `* { box-sizing:
           content-box }` в voice-page.css, из-за него `min-height: 44px`
           превращалось в 43 px по замеру. ---------------------------- */
        body.voice-page * {
            box-sizing: border-box;
        }
        body.voice-page button {
            min-height: 44px;
        }

        /* --- Пустой голосовой канал: кнопка приглашения и «Подключиться» ---
           В приложении это .voice-btn (43 px по замеру на 390 px). ------- */
        .voice-btn,
        .voice-empty button {
            min-height: 44px;
        }

        /* --- Подсказка о правах доступа: кнопки «Проверить снова» --- */
        .voice-permission-hint button {
            min-height: 44px;
        }

        /* --- Ввод сообщения в чате канала и в беседе -----------------
           Поле 36 px в высоту, а это главный орган управления на
           телефоне: поднимаем до цели касания. Ширина не меняется,
           потому что она и так во всю строку. ----------------------- */
        .input-wrapper input,
        .input-wrapper textarea {
            min-height: 44px;
        }
    }
}

/* =========================================================
   ПОИСК ПО СООБЩЕНИЯМ НА УЗКОМ ЭКРАНЕ
   =========================================================
   Панель занимает ширину окна (left/right по 8 px), поэтому не вылезает
   за экран и не даёт горизонтальной прокрутки. Кнопки — не меньше 44 px:
   на телефоне по ним попадают пальцем. Длинные строки результата
   переносятся (overflow-wrap в app.css), список прокручивается сам. */
@media (max-width: 768px) {
    @layer components {
        .message-search-panel {
            top: 48px;
            left: 8px;
            right: 8px;
            width: auto;
            max-height: min(65vh, 480px);
        }

        /* Закреплённые: та же геометрия, что у поиска, — панель на всю
           ширину окна и цели касания 44 px. */
        .pinned-panel {
            top: 48px;
            left: 8px;
            right: 8px;
            width: auto;
            max-height: min(65vh, 480px);
        }

        .chat-search-btn,
        .chat-pin-btn,
        .pinned-panel-close,
        .message-search-close {
            width: 44px;
            height: 44px;
        }

        .message-search-result,
        .pinned-item {
            min-height: 44px;
        }
    }
}

/* =========================================================
   ШАПКА ГОЛОСОВОГО КАНАЛА НА УЗКОМ ЭКРАНЕ
   =========================================================
   Замер ДО на 390 px: `.main-header` — строка высотой 56 px, а общее
   правило «заголовок на всю строку» растягивало КАЖДЫЙ `span`, включая
   обёртку иконки. Три ребёнка с `flex-basis: 100%` сжимались до трети
   ширины каждый: заголовок «Voice» уезжал в середину (129 px), а мета
   («Общий · 4 участника · в канале 1 мин · связь хорошая», 12 px)
   получала колонку 108 px, переносилась на четыре строки и имела
   прямоугольник 108×65 с верхом 51 — то есть выходила и выше, и ниже
   шапки. Здесь у шапки голоса СВОЙ порядок: [иконка] [заголовок] в первой
   строке, мета — второй строкой на всю ширину (после правки: 374×17).
   Высота шапки растёт по содержимому, поэтому мета не может наехать ни
   на заголовок, ни на область под шапкой. Шире 768 px правила не
   действуют. */
@media (max-width: 768px) {
    @layer layout {
        .voice-header {
            height: auto;
            min-height: 56px;
            flex-wrap: wrap;
            align-items: center;
            row-gap: 2px;
            padding-top: 6px;
            padding-bottom: 6px;
        }
        /* Тот же селектор, что у общего правила выше по файлу (оно даёт
           каждому span `flex-basis: 100%`), только с классом `.voice-header`:
           специфичность равна, а правило ниже — поэтому выигрывает. */
        .voice-header > span:not(.voice-header-meta):not(.hash):not(.dm-peer-avatar):not(.dm-peer-status) {
            order: 0;
            flex: 0 0 auto;
            min-width: 0;
        }
        .voice-header > #voice-channel-name {
            order: 0;
            flex: 1 1 auto;
            min-width: 0;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
        .voice-header > .voice-header-meta {
            order: 1;
            flex: 1 1 100%;
            margin-left: 0;
            padding-left: 28px;
            font-size: 11px;
            line-height: 1.35;
            white-space: normal;
            overflow-wrap: anywhere;
        }
    }

    /* =============================================================
       ОКНО «УПРАВЛЕНИЕ СЕРВЕРОМ»: ВКЛАДКИ, УЧАСТНИКИ, КОМНАТЫ
       =============================================================
       Замер ДО на 390 px: вкладка «Приглашения» кончалась на 405 px при
       окне 390 (15 px за краем, кнопка обрезана `overflow: hidden`);
       в строке участника кластер ролей доходил до 586 px (+196 px), а
       «+ Участник» и «+ Модератор» были недоступны; в строке комнаты
       «Снять» вылезала на 8 px. Причина — flex-строки без переноса:
       у участника в одну линию стояли аватар, имя, роли и кнопки, у
       комнаты — поле имени и кластер из сёлекта и трёх кнопок.
       На узком экране строки становятся многострочными: имя, затем
       роли, затем действия; действия переносятся, а не выдавливаются.
       Вкладки тоже переносятся: четыре подписи в 390 px не влезают.
       Шире 768 px — прежняя сетка из roles.css без единого отличия. */
    @layer components {
        .tv-manage-tabs {
            display: grid;
            grid-template-columns: repeat(2, minmax(0, 1fr));
            gap: 6px;
            padding: 8px 12px;
        }
        #server-manage-modal .dm-tab,
        #channel-roles-modal .dm-tab {
            width: 100%;
            min-height: 44px;
        }
        #server-manage-modal .tv-manage-body,
        #channel-roles-modal .tv-manage-body {
            padding: 12px;
        }
        /* Основные кнопки и поля окна — цели касания. Мелкие галочки прав
           живут внутри подписей `.tv-perm` (у них своя высота 44 px),
           поэтому `input[type=checkbox]`/`radio` сюда не входят. */
        #server-manage-modal .tv-manage-body button,
        #channel-roles-modal .tv-manage-body button {
            min-height: 44px;
        }
        #server-manage-modal .tv-manage-body input:not([type="checkbox"]):not([type="radio"]),
        #server-manage-modal .tv-manage-body select,
        #channel-roles-modal .tv-manage-body input:not([type="checkbox"]):not([type="radio"]),
        #channel-roles-modal .tv-manage-body select {
            min-height: 44px;
        }

        #server-manage-modal .tv-member-row,
        #channel-roles-modal .tv-member-row {
            flex-wrap: wrap;
            align-items: center;
            row-gap: 8px;
        }
        #server-manage-modal .tv-member-name,
        #channel-roles-modal .tv-member-name {
            flex: 1 1 120px;
        }
        #server-manage-modal .tv-member-roles,
        #channel-roles-modal .tv-member-roles {
            flex: 1 1 100%;
        }
        #server-manage-modal .tv-member-row .tv-role-actions,
        #channel-roles-modal .tv-member-row .tv-role-actions {
            flex: 1 1 100%;
            flex-wrap: wrap;
            row-gap: 6px;
        }
        #server-manage-modal .tv-member-row .tv-role-actions .btn-pill,
        #channel-roles-modal .tv-member-row .tv-role-actions .btn-pill {
            flex: 1 1 auto;
            min-height: 44px;
        }
        #server-manage-modal .tv-role-chip,
        #channel-roles-modal .tv-role-chip {
            max-width: 100%;
            overflow: hidden;
            text-overflow: ellipsis;
        }

        #server-manage-modal .tv-channel-actions,
        #channel-roles-modal .tv-channel-actions {
            flex-wrap: wrap;
            row-gap: 8px;
        }
        #server-manage-modal .tv-channel-actions select,
        #channel-roles-modal .tv-channel-actions select {
            flex: 1 1 100%;
            min-width: 0;
            max-width: none;
            min-height: 44px;
        }
        #server-manage-modal .tv-channel-actions .btn-pill,
        #channel-roles-modal .tv-channel-actions .btn-pill {
            flex: 1 1 auto;
            min-height: 44px;
        }
        #server-manage-modal .tv-manage-row .btn-pill,
        #server-manage-modal .tv-manage-row select,
        #channel-roles-modal .tv-manage-row .btn-pill,
        #channel-roles-modal .tv-manage-row select {
            min-height: 44px;
        }
        #channel-roles-modal .tv-cr-role-row .tv-role-actions .btn-pill,
        #channel-roles-modal .tv-cr-member-row .btn-pill {
            min-height: 44px;
        }
        #server-manage-modal .tv-manage-form-details > summary,
        #channel-roles-modal .tv-manage-form-details > summary {
            min-height: 44px;
            display: flex;
            align-items: center;
        }

        /* Поле настроек в окне: подпись и сёлект в одну строку не влезают —
           сёлект сжимался до 68 px и показывал «Микрофон по у…». На узкой
           ширине подпись занимает свою строку, поле — следующую. */
        #settings-modal label.field {
            flex-wrap: wrap;
            row-gap: 6px;
        }
        #settings-modal label.field > select {
            flex: 1 1 100%;
            width: 100%;
        }

        /* Кнопка вложения в композере: 40x42 px — на палец мимо. */
        .attach-button {
            min-width: 44px;
            min-height: 44px;
        }

        /* --- Личные сообщения: вкладки, поле и кнопка заявки ------ */
        .dm-tab,
        #btn-dm-friends,
        .dm-add-friend,
        #dm-add-friend-btn {
            min-height: 44px;
        }

        /* --- Редактор аватара -------------------------------------
           Замер ДО на 390x640: кадр 253×253, ряд действий высотой 35 px
           заканчивался на 690 px при окне 640 — «Сохранить»/«Отмена»
           редактора оставались ПОД нижней кромкой, а форма профиля уезжала
           под них. Теперь кадр ограничен и по ВЫСОТЕ окна
           (`min(320px, 34vh)`; на коротком окне — 30vh, то есть 192 px),
           ряд действий 44 px и липнет к нижней кромке прокручиваемого окна,
           а кнопка сохранения формы достигается прокруткой. На 390x844
           кадр не меняется (253 px — его и так ограничивает ширина окна),
           но кнопки становятся целями касания. Шире 768 px — прежний вид. */
        .avatar-preview.is-cropping {
            max-width: min(320px, 34vh);
        }
        .tv-avatar-crop-actions {
            position: sticky;
            bottom: 0;
            z-index: 2;
            padding: 8px 0;
            background: var(--bg-panel);
        }
        .tv-avatar-crop-actions .btn-primary,
        .tv-avatar-crop-actions .btn-secondary,
        .tv-avatar-crop-zoom {
            min-height: 44px;
        }
        .tv-avatar-crop-zoom {
            min-width: 44px;
        }
        #profile-modal .modal-content .form-input,
        #profile-modal .modal-content .form-group input,
        #profile-modal .modal-content .form-group textarea,
        .profile-page .form-actions .btn-primary,
        .profile-page .form-actions .btn-secondary,
        #profile-modal .modal-content .form-actions .btn-primary,
        #profile-modal .modal-content .form-actions .btn-secondary {
            min-height: 44px;
        }

        /* --- Шапка канала и личной беседы: ОДНА строка ----------------
           Замер ДО на 390 px: общее правило делало заголовок строкой на всю
           ширину, а иконку/решётку выносило в конец. У канала решётка «#»
           уезжала к кнопке поиска, у беседы пустой слот иконки занимал
           треть ширины, и ник собеседника начинался с 123 px. Здесь
           порядок как на широком экране: [иконка/решётка] [заголовок]
           [аватар] [статус] [поиск], заголовок забирает остаток и
           сокращается многоточием. Слот остаётся пустым, если он пуст. */
        .main-header:has(> #chat-channel-name, > #dm-peer-name) {
            height: auto;
            min-height: 56px;
            flex-wrap: nowrap;
        }
        .main-header:has(> #chat-channel-name, > #dm-peer-name) > span.hash,
        .main-header:has(> #chat-channel-name, > #dm-peer-name) > .dm-peer-avatar,
        .main-header:has(> #chat-channel-name, > #dm-peer-name) > .dm-peer-status {
            order: 0;
            flex: 0 0 auto;
        }
        /* Пустой слот иконки у беседы: в разметке он есть всегда, но класса у
           него нет, поэтому общее правило выше растягивало ЕГО на 210 px —
           ник собеседника начинался с середины шапки. */
        .main-header:has(> #dm-peer-name) > span:empty {
            display: none;
        }
        .main-header:has(> #chat-channel-name, > #dm-peer-name) > #chat-channel-name,
        .main-header:has(> #dm-peer-name) > #dm-peer-name {
            order: 0;
            flex: 1 1 auto;
            min-width: 0;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        /* --- Приглашение: крестик и кнопки — цели касания ------------ */
        .invite-modal-close {
            min-width: 44px;
            min-height: 44px;
        }
        .invite-modal-actions .btn-pill {
            min-height: 44px;
        }

        /* --- Окно управления: поле цвета роли ----------------------- */
        #tv-role-color {
            min-width: 44px;
            min-height: 44px;
        }

        /* --- Кнопка «+» у заголовка списка каналов ------------------
           Замер на 360: 25x24 px. Это главная кнопка создания канала в
           шторке — на палец мимо. */
        .add-channel {
            min-width: 44px;
            min-height: 44px;
        }

        /* --- Поиск по сообщениям: поле и выбор области --------------- */
        .message-search-input,
        .message-search-scope {
            min-height: 44px;
        }

        /* --- Просмотр картинки: крестик и стрелки ------------------
           Размер задан в attachments.css (немедийное правило 44x44, а на
           ≤600 px было 40x40) — здесь он не дублируется, см. правку там. */

        /* --- Лендинг: компактная кнопка в шапке 40 px ---------------- */
        body.home-page .lp-btn {
            min-height: 44px;
        }

        /* --- Общее для всплывающих окон: кнопки, поля и сёлекты ------
           Окна делят оболочку `.settings-modal`, но содержимое рисуют разные
           модули (настройки, управление сервером, приглашение, источники
           демонстрации). Общее правило экономит пять частных и держит один
           порог: 44 px по высоте у всего, что нажимают в окне. -------- */
        .settings-modal button,
        .settings-modal select,
        .settings-modal input[type="text"],
        .settings-modal input[type="number"],
        .settings-modal input[type="search"] {
            min-height: 44px;
        }
    }
}

/* Короткое окно (телефон с адресной строкой): ряд действий редактора
   аватара прилипает к низу, и кадр 34vh оставлял подсказку под кнопками.
   На коротком окне кадр ужимается до 30vh — подсказка и кнопки встают
   друг под другом без наложения; на 390x844 правило не действует. */
@media (max-width: 768px) and (max-height: 720px) {
    @layer components {
        .avatar-preview.is-cropping {
            max-width: min(320px, 30vh);
        }

        .tv-avatar-crop-hint {
            margin-bottom: 0;
        }
    }
}

/* ------------------------------------------------------------------
   Долгое нажатие на телефоне (iOS Safari).
   iOS не присылает `contextmenu` для обычных элементов и вместо меню
   показывает системный callout (выделение/«Скопировать»/«Поделиться»).
   Свой long-press живёт в `static/js/lib/context-menu.js`; здесь только
   убираем системный callout на тех поверхностях, где есть меню.
   ВАЖНО: `user-select: none` стоит ТОЛЬКО на служебных частях (аватар,
   шапка, кнопки, реакции, вложения), а `.msg-text` и текст закреплённого
   остаются выделяемыми. Поэтому копирование текста мышью/двойным тапом
   не сломано; пункт «Копировать текст» в меню — второй путь.
   ------------------------------------------------------------------ */
@layer base {
    @media (pointer: coarse) {
        .msg,
        .member[data-user-id],
        .voice-member-in-sidebar,
        .voice-tile,
        .channel,
        .server-icon[data-server-id],
        .dm-conversation[data-conversation-id],
        .dm-friend[data-user-id],
        .pinned-item[data-message-id] {
            -webkit-touch-callout: none;
        }

        /* Текст сообщения и закреплённого — выделяемый. Явно, чтобы будущее
           общее правило на родителе не отняло копирование. */
        .msg-text,
        .msg-text *,
        .msg-code,
        .msg-pre,
        .pinned-item-text {
            -webkit-user-select: text;
            user-select: text;
            -webkit-touch-callout: none;
        }

        /* Служебные части: с них нечего копировать, а выделение только
           мешает долгому нажатию и тапу. */
        .msg .msg-avatar,
        .msg .msg-head,
        .msg .msg-actions,
        .msg .msg-reactions,
        .msg .msg-attachments,
        .member .m-avatar,
        .member .status-dot,
        .pinned-item-top,
        .pinned-item-meta,
        .dm-conv-avatar,
        .dm-conv-time {
            -webkit-user-select: none;
            user-select: none;
        }
    }
}
