/* ═══════════════════════════════════════════════════════════════════════
   МОБИЛЬНЫЙ СЛОЙ ПОРТАЛА
   ═══════════════════════════════════════════════════════════════════════

   Назначение. Единственное место, куда складываются мобильные @media всего
   проекта. Файл подключается в <head> ПОСЛЕДНИМ (см. base.html), поэтому
   работает как слой переопределений: правила custom.css и постраничных
   стилей остаются на месте нетронутыми — их фиксируют текстовые тесты, и
   любой перенос или удаление красит сьюту.

   Шкала брейкпоинтов проекта: 576 / 768 / 992 / 1200 (набор Bootstrap 5.3).
   Других порогов не вводить. Верхняя граница диапазона записывается как
   991.98px — это принятая в Bootstrap запись «ниже 992», она же уже
   используется в custom.css (1199.98 / 767.98 / 479.98).

   Содержимое этого файла — каркас: шторка навигации, мобильная панель с
   бургером, отступы шапки и области содержимого, панель уведомлений.
   Прикладные разделы (календарь, оргструктура, опросы, таблицы) добавляются
   сюда отдельными пакетами.
   ═══════════════════════════════════════════════════════════════════════ */

:root {
    /* Безопасные зоны телефона (вырез, скруглённые углы, домашняя черта).
       Работают только вместе с `viewport-fit=cover` в meta viewport
       (app/templates/base.html). Фолбэк 0px обязателен: на десктопе и в
       браузерах без поддержки env() выражение без него становится
       невалидным целиком и обнуляет всё правило. */
    --safe-top:    env(safe-area-inset-top, 0px);
    --safe-right:  env(safe-area-inset-right, 0px);
    --safe-bottom: env(safe-area-inset-bottom, 0px);
    --safe-left:   env(safe-area-inset-left, 0px);

    /* Ширина выдвижной шторки. Ограничена долей экрана, чтобы на 320px
       под затемнением оставалась видимая полоса контента. */
    --mnav-width: min(280px, 84vw);

    /* Высота верхней строки портала ниже 992px: бургер 44px (принятый в
       проекте минимум области нажатия) плюс по 4px воздуха сверху и снизу.
       В этой же строке стоят заголовок раздела и колокольчик, поэтому
       величина одна на каркас, шапки разделов и обе кнопки. */
    --mhead-h: 52px;
}

/* ─── Элементы, которых на десктопе нет вовсе ────────────────────────
   Разметка отдаётся сервером всегда (условий по User-Agent в проекте нет и
   заводить их не нужно), поэтому выше 992px три узла просто выключены. */
.mnav-overlay,
.mobile-bar,
.sidebar-close {
    display: none;
}


/* ═══════════════════════════════════════════════════════════════════
   НИЖЕ 992px — КАРКАС
   ═══════════════════════════════════════════════════════════════════ */
@media (max-width: 991.98px) {

    /* ─── K1. Сайдбар уезжает за левый край ──────────────────────────
       Панель остаётся `position: fixed` из custom.css — меняется только
       её ширина и сдвиг. Прокрутка сайдбара (overflow-y: auto) и стиль
       его скроллбара не трогаются: они зафиксированы
       tests/test_layout_viewport_height.py. */
    .app-sidebar {
        width: var(--mnav-width);
        transform: translateX(-100%);
        transition: transform .22s ease;

        /* K4. Низ шторки недостижим. В custom.css у сайдбара
           `min-height: 100vh`, тогда как каркас живёт в 100dvh. На телефоне
           100vh — это высота БЕЗ адресной строки, поэтому нижние 60–115px
           фиксированной панели («Светлая тема», «Повторить тур», «Выйти»)
           уходили под неё. Здесь минимум пересчитывается на видимый
           вьюпорт; строка на vh оставлена фолбэком для движков без dvh. */
        min-height: 100vh;
        min-height: 100dvh;

        /* Ландшафтная ориентация с вырезом слева. `box-sizing` объявлен явно:
           глобальный border-box приходит из Bootstrap, а он подключается с
           CDN — при его недоступности отступ распёр бы панель. */
        box-sizing: border-box;
        padding-left: var(--safe-left);

        /* Шторка прибита к `top: 0` (custom.css) и перекрывает полосу
           состояния целиком. В браузере под ней адресная строка, а в режиме
           приложения — вырез и часы: логотип и крестик закрытия оказывались
           под ними. Верх панели опускается ниже системных элементов. */
        padding-top: var(--safe-top);
    }

    body.app-layout.mnav-open .app-sidebar {
        transform: translateX(0);
    }

    /* Низ панели поднимается над домашней чертой iPhone. */
    .sidebar-footer {
        padding-bottom: calc(.75rem + var(--safe-bottom));
    }

    /* Крестик закрытия — поверх логотипа справа. `.sidebar-brand`
       становится позиционирующим контекстом только на мобильном. */
    .sidebar-brand {
        position: relative;
        padding-right: 3rem;
    }

    .sidebar-close {
        display: flex;
        align-items: center;
        justify-content: center;
        position: absolute;
        top: 50%;
        right: .5rem;
        transform: translateY(-50%);
        width: 40px;
        height: 40px;
        padding: 0;
        border: none;
        border-radius: 10px;
        background: rgba(217, 225, 255, .08);
        color: rgba(217, 225, 255, .75);
        font-size: 1rem;
        cursor: pointer;
    }

    /* ─── K5. Матовое стекло шторки ──────────────────────────────────
       Фон панели в custom.css — rgba(0, 0, 20, .7), и на телефоне этого
       мало: под открытой шторкой лежит страница во всю ширину экрана, её
       заголовки и таблицы читались сквозь панель и накладывались на пункты
       меню. На компьютере эффекта не видно — там под панелью пустая колонка
       шириной 232px, а не содержимое.

       Панель становится плотнее и получает размытие фона: сквозь неё видно
       не текст, а размытое пятно, поэтому пункты меню читаются уверенно.
       Правило живёт только ниже 992px — на компьютере фон панели прежний.

       `-webkit-backdrop-filter` продублирован ради Safari: без префикса он
       понимает свойство только с 18-й версии, а на iOS это основной движок. */
    .app-sidebar {
        background: rgba(0, 0, 20, .88);
        backdrop-filter: blur(20px);
        -webkit-backdrop-filter: blur(20px);
    }

    /* ─── K5-бис. Запасной вариант без размытия ──────────────────────
       Движок без `backdrop-filter` (Firefox до 103, встроенные веб-вью
       старых Android) полупрозрачный фон применит, а размытие — нет, и
       просвечивание вернулось бы ровно там, где его чинили. Там подложка
       становится полностью непрозрачной — проступать нечему. */
    @supports not ((backdrop-filter: blur(20px))
                   or (-webkit-backdrop-filter: blur(20px))) {
        .app-sidebar {
            background: #000014;
        }
    }

    /* ─── Затемнение под шторкой ─────────────────────────────────────
       Ниже сайдбара (z-index: 200) и выше всего остального каркаса.
       В закрытом состоянии не только прозрачное, но и `visibility: hidden` —
       иначе оно перехватывало бы касания по всему экрану. */
    .mnav-overlay {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 190;
        background: rgba(2, 10, 10, .72);
        opacity: 0;
        visibility: hidden;
        transition: opacity .22s ease, visibility .22s ease;

        /* Фон под открытой шторкой не прокручивается: узел лежит прямо в
           `body.app-layout` с его overflow: hidden, поэтому колесо и жест
           над затемнением не доходят до .page-content. */
        touch-action: none;
        overscroll-behavior: contain;
    }

    body.app-layout.mnav-open .mnav-overlay {
        opacity: 1;
        visibility: visible;
    }

    /* ─── K1. Колонка занимает всю ширину экрана ─────────────────────
       Ради этой строки и затевался пакет: на 375px под содержимое
       оставалось 143px из 375. */
    .app-main {
        margin-left: 0;

        /* ─── H1. Место под верхнюю строку ───────────────────────────
           Полоса управления (бургер и колокольчик) выведена из потока,
           поэтому высоту под неё отводит колонка. Шапка раздела это место
           ЗАБИРАЕТ обратно отрицательным `margin-top` (правило H2) — тогда
           её заголовок встаёт в ту же строку, что бургер и колокольчик, и
           вторая полоса сверху исчезает.

           Страницы без шапки (`polls/complete.html`) и заглушка раздела
           «только для компьютера» отступ не забирают и просто начинаются
           ниже строки — отдельных правил под них не нужно. */
        padding-top: calc(var(--safe-top) + var(--mhead-h));
    }

    /* ─── Полоса управления ──────────────────────────────────────────
       Раньше это была вторая строка сверху: собственная панель с бургером и
       логотипом, а под ней шапка раздела. Вдвоём они съедали 104–170px до
       начала содержимого, причём логотип повторял логотип шторки.

       Теперь панель — не строка, а слой: она выведена из потока и красит
       только полосу под системными элементами (вырез, часы), высота которой
       и равна `--safe-top`; в обычном браузере с адресной строкой это ноль.
       Бургер и колокольчик стоят поверх строки заголовка раздела.

       `position: fixed` вместо `absolute` — сознательно: у `.app-main`
       остаётся `overflow-x: clip` из custom.css, и фиксированный элемент из
       него не срезается (тот же приём, что у выпадашки уведомлений ниже).
       Заодно бургер не уезжает ни при какой прокрутке — прокручивается
       `.page-content`, но независимость от неё теперь гарантирована слоем.

       `pointer-events: none` — чтобы прозрачная часть полосы не перехватывала
       нажатия по заголовку и кнопкам шапки под ней; сам бургер нажатия
       принимает (правило ниже).

       Фон, вырез и `z-index` сохранены прежними: цвет совпадает с
       `theme-color` и с `theme_color` манифеста (полоса состояния не должна
       вспыхивать чужим цветом), а 25 — уровень между шапкой раздела и
       затемнением шторки. */
    .mobile-bar {
        display: flex;
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        height: var(--safe-top);
        padding-left: var(--safe-left);
        padding-right: var(--safe-right);
        background: #0a0a18;
        z-index: 25;
        pointer-events: none;
    }

    /* Бургер прибит к левому краю строки заголовка. Размеры и вид прежние —
       переехало только позиционирование. */
    .mobile-bar__burger {
        display: flex;
        align-items: center;
        justify-content: center;
        position: fixed;
        top: calc(var(--safe-top) + (var(--mhead-h) - 44px) / 2);
        left: calc(.75rem + var(--safe-left));
        width: 44px;
        height: 44px;
        padding: 0;
        border: 1px solid rgba(217, 225, 255, .12);
        border-radius: 11px;
        background: rgba(217, 225, 255, .06);
        color: rgba(217, 225, 255, .85);
        font-size: 1.25rem;
        line-height: 1;
        cursor: pointer;
        pointer-events: auto;
    }

    /* Логотип из полосы убран: строку занимает заголовок раздела, а сам
       логотип никуда не делся — он в шапке выдвижной шторки, куда ведёт
       соседний бургер. Разметка остаётся (на компьютере полосы нет вовсе). */
    .mobile-bar__brand {
        display: none;
    }

    /* ─── H2. Шапка раздела — единственная строка сверху ─────────────
       Шапки объявлены в 45 шаблонах и устроены по-разному: где-то только
       заголовок, где-то заголовок с подзаголовком, где-то ещё и ряд кнопок.
       Отсюда и разнобой: высота строки, отступы и кегль плыли от раздела к
       разделу, а колокольчик — последний элемент шапки — оказывался то
       справа от заголовка, то под кнопками, то у нижнего края.

       Правило приводит все шапки к одной геометрии и поднимает их на место
       бывшей мобильной панели:

       · `order: -1` — шапка становится первым элементом колонки, даже когда
         сервер отдал флеш-сообщения (в разметке они объявлены выше неё);
       · `margin-top` ровно гасит отступ, отведённый под полосу в K1/H1, —
         строка заголовка встаёт под системную полосу, а бургер и
         колокольчик оказываются в ней же;
       · `min-height` держит одинаковую высоту строки независимо от того,
         есть у раздела подзаголовок или нет.

       Правило одно на портал: шапку всех разделов, включая оргструктуру,
       отдаёт общий блок `_page_header.html`. */
    .app-topbar {
        box-sizing: border-box;
        order: -1;
        align-items: center;
        gap: .5rem;
        row-gap: .4rem;
        min-height: calc(var(--safe-top) + var(--mhead-h));
        margin-top: calc(-1 * (var(--safe-top) + var(--mhead-h)));
        padding: calc(var(--safe-top) + .35rem) calc(.75rem + var(--safe-right))
                 .35rem calc(.75rem + var(--safe-left));
    }

    /* Блок заголовка (первый элемент шапки во всех 45 шаблонах) занимает
       строку целиком и оставляет поля под бургер слева и колокольчик справа.
       Отсюда два следствия: заголовок не наезжает на кнопки, а ряд действий
       переносится на свою строку и больше не двигает заголовок.

       `flex-basis: 100%` не действует на справочнике сотрудников — там у
       блока инлайновый `flex: 1`, который перебивает таблицу стилей. Ниже
       992px раздел всё равно показывает заглушку (см. пакет U). */
    .app-topbar > *:first-child {
        box-sizing: border-box;
        flex: 1 1 100%;
        min-width: 0;
    }

    /* Заголовок оставляет поля под бургер слева и колокольчик справа — обе
       кнопки висят вне потока в первой строке. Поля именно у заголовка, а не
       у всей строки шапки: ряд действий уходит на СВОЮ строку ниже обеих
       кнопок, и отступ там только съел бы ширину.

       `min-height` — вторая половина того же: без него короткий заголовок
       давал строку в ~20px, ряд действий начинался сразу под ней и заезжал
       под колокольчик, который занимает первые 44px. Теперь ряд действий
       начинается ниже обеих кнопок, и между ними остаётся видимый зазор
       (`row-gap` строки шапки). */
    .page-head {
        box-sizing: border-box;
        display: flex;
        align-items: center;
        flex: 1 1 100%;
        min-width: 0;
        min-height: 44px;
        padding-left: calc(44px + .5rem);
        /* Справа теперь не один колокольчик, а группа из трёх элементов:
           36px колокольчик + отступ + кнопка баллов + отступ + 36px
           фотография. Величина округлена вверх — кнопка баллов растёт от
           числа знаков, и заголовок не должен заезжать под неё на
           четырёхзначном счёте. */
        padding-right: calc(148px + .5rem);
    }

    /* Ряд действий — отдельная строка во всю ширину. */
    .page-actions {
        flex: 1 1 100%;
        margin-left: 0;
    }

    /* Кнопка главного действия — цель для пальца. */
    .btn-action {
        min-height: 44px;
    }

    /* Кегль заголовка: 1.15rem с компьютера разворачивал в две строки почти
       любое название раздела на 375px. */
    .app-topbar h1 {
        font-size: 1rem;
        line-height: 1.25;
    }

    .app-topbar .subtitle {
        font-size: .72rem;
        line-height: 1.3;
    }

    /* ─── K2. Отсечение без прокрутки ────────────────────────────────
       `.app-main { overflow-x: clip }` при `overflow-y: visible` не
       прокручивает, а отсекает: всё, что вылезло за колонку и лежит ВНЕ
       `.page-content` (флеш-блок, каждая `.app-topbar`, выпадашка
       уведомлений), было физически недостижимо. Правило из custom.css не
       трогаем — оно нужно на десктопе и зафиксировано тестами. Вместо
       этого убираем саму причину переполнения: после снятия margin-left
       колонка равна ширине экрана, и достаточно, чтобы содержимое шапки
       ПЕРЕНОСИЛОСЬ, а не выходило за край.

       У самой `.app-topbar` перенос уже есть (custom.css), а у вложенных
       групп — нет: в шаблонах это `<div class="d-flex gap-2">` и формы,
       которые собираются в неразрывный ряд по 400px и больше. */
    .app-topbar > div,
    .app-topbar .d-flex,
    .app-topbar .input-group,
    .app-topbar form {
        flex-wrap: wrap;
        min-width: 0;
    }

    /* Длинный заголовок шапки не должен распирать колонку неразрывным
       словом (UPN, адрес, имя файла). */
    .app-topbar h1,
    .app-topbar .subtitle {
        overflow-wrap: anywhere;
    }

    .page-content {
        padding: 1rem;
        padding-bottom: calc(1rem + var(--safe-bottom));
    }

    /* ─── K6. Блок флеш-сообщений ────────────────────────────────────
       Инлайновый стиль заменён классом (custom.css), поэтому отступы
       наконец переопределяемы. Неразрывный токен внутри сообщения
       (UPN, URL) раньше уезжал за колонку и отсекался K2. */
    .app-flash {
        padding: .6rem 1rem 0;
    }

    .app-flash .alert {
        overflow-wrap: anywhere;
    }

    /* ─── H3. Правая группа шапки — постоянное место в верхней строке ──
       Разметка подключается партиалами внутрь шапки страницы (см.
       `_page_header.html`), и место на экране у неё до этой правки зависело
       от того, как устроена шапка конкретного раздела — от строки под
       логотипом до нижнего края шапки.

       Разметка не трогается: ниже 992px группа выводится из потока и встаёт
       в правый край верхней строки — в одну линию с бургером, на каждой
       странице в одном и том же месте. Выше 992px правило не действует.

       Селекторов два, потому что мест два. `.topbar-right` — обычная
       страница: из потока выводится ВСЯ тройка, иначе колокольчик остался бы
       в верхней строке, а баллы с фотографией уехали на следующую.
       `.notif-bell-wrap` — страница без шапки (`polls/complete.html`), где
       колокольчик подключается в одиночку прямым потомком `.app-main`; в
       группе он снова становится обычным элементом строки (правило ниже).

       `margin: 0` обязателен: у страницы без шапки notifications.css задаёт
       колокольчику поля 14px/28px, которые сместили бы фиксированную
       позицию.

       `z-index` не выставляется намеренно. Без него `position: fixed` не
       создаёт слоя, и выпадашка остаётся ровно в том же контексте
       наложения, что и до правки (её починку фиксируют правила ниже). */
    .notif-bell-wrap,
    .topbar-right {
        position: fixed;
        top: calc(var(--safe-top) + (var(--mhead-h) - 36px) / 2);
        right: calc(.75rem + var(--safe-right));
        margin: 0;
    }

    .topbar-right .notif-bell-wrap {
        position: static;
    }

    /* Отступы внутри группы и кнопка баллов ужимаются: место в верхней
       строке делится на троих, а слева от них ещё и заголовок раздела. */
    .topbar-right {
        gap: .4rem;
    }

    .topbar-points {
        padding: 0 .5rem;
        font-size: .78rem;
    }

    /* ─── Меню под фотографией ───────────────────────────────────────
       На всю ширину экрана и поверх содержимого — тем же приёмом, что и
       выпадашка уведомлений: `position: fixed` выводит меню из-под
       клиппирования `.app-main`, а `top: auto` оставляет его на статической
       позиции, то есть ровно под кнопкой с фотографией. */
    .topbar-user__menu {
        position: fixed;
        top: auto;
        left: calc(.5rem + var(--safe-left));
        right: calc(.5rem + var(--safe-right));
        width: auto;
        max-width: none;
        margin-top: .5rem;
        max-height: calc(100dvh - 7rem - var(--safe-bottom));
        overflow-y: auto;
        overscroll-behavior: contain;
    }

    /* Пункты меню — цель для пальца, принятый в проекте минимум. */
    .topbar-user__item {
        min-height: 44px;
    }

    /* ─── Выпадашка уведомлений ──────────────────────────────────────
       Панель шириной 360px лежала внутри клиппирующей `.app-main`, а
       `.app-topbar` со `sticky` и `z-index` создаёт stacking context —
       левый край панели просто срезался. `position: fixed` выводит её из-под
       клиппирования: containing block фиксированного элемента — вьюпорт, а
       ни `overflow: clip`, ни `position: sticky` его не перехватывают.

       `top: auto` оставляет вертикаль на статической позиции, то есть ровно
       под колокольчиком, где бы шапка ни оказалась после переноса строк.
       Горизонталь задана обоими краями — панель занимает всю доступную
       ширину. Разметка колокольчика при этом не меняется (её фиксирует
       tests/test_notif_bell.py), notifications.css не трогается. */
    .notif-dropdown {
        position: fixed;
        top: auto;
        left: calc(.5rem + var(--safe-left));
        right: calc(.5rem + var(--safe-right));
        width: auto;
        max-width: none;
        /* Зазор от колокольчика: при `top: auto` статическая позиция задаёт
           верх ВНЕШНЕГО края блока, поэтому margin работает как обычно. */
        margin-top: .5rem;
        /* Домашняя черта вычитается отдельно: панель растёт вниз от
           колокольчика, и в режиме приложения последний пункт списка
           упирался ровно в неё. */
        max-height: calc(100dvh - 7rem - var(--safe-bottom));
        overflow-y: auto;
        overscroll-behavior: contain;
    }

    .notif-dropdown__body {
        max-height: none;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   НИЖЕ 992px — СВЕТЛАЯ ТЕМА
   Новые узлы каркаса рисуются на тёмной палитре; на белом фоне им нужны
   свои цвета, как и остальным элементам портала в custom.css.
   ═══════════════════════════════════════════════════════════════════ */
@media (max-width: 991.98px) {

    /* Полоса красит только зону под системными элементами: нижней границы
       у неё больше нет — её рисует шапка раздела в той же строке. */
    [data-theme="light"] .mobile-bar {
        background: #fff;
    }

    [data-theme="light"] .mobile-bar__burger {
        background: color-mix(in srgb, var(--ac) 8%, transparent);
        border-color: color-mix(in srgb, var(--ac) 22%, transparent);
        color: var(--rc-blue);
    }

    [data-theme="light"] .sidebar-close {
        background: rgba(0, 0, 0, .05);
        color: rgba(0, 0, 0, .55);
    }

    /* Матовое стекло шторки на белом фоне: тон светлый, плотность и
       размытие те же, что на тёмной теме (правило K5). Своё правило здесь
       обязательно: `[data-theme="light"] .app-sidebar` из custom.css
       специфичнее одиночного класса, и без этой пары светлая тема осталась
       бы с прежним фоном и вовсе без размытия. */
    [data-theme="light"] .app-sidebar {
        background: rgba(255, 255, 255, .88);
        backdrop-filter: blur(20px);
        -webkit-backdrop-filter: blur(20px);
    }

    /* Тот же запасной вариант, что и на тёмной теме: без размытия фон
       панели непрозрачный. */
    @supports not ((backdrop-filter: blur(20px))
                   or (-webkit-backdrop-filter: blur(20px))) {
        [data-theme="light"] .app-sidebar {
            background: #fff;
        }
    }

    /* Затемнение справа от шторки плотнее исходного: на светлой теме оно
       единственное, что отделяет меню от страницы. */
    [data-theme="light"] .mnav-overlay {
        background: rgba(2, 10, 10, .55);
    }
}


/* ═══════════════════════════════════════════════════════════════════
   НИЖЕ 992px — ТАЧ-ТАРГЕТЫ
   ═══════════════════════════════════════════════════════════════════

   Задача: довести эффективную область нажатия интерактивных элементов до
   44×44px. Приёмов ровно два, и выбор между ними не вкусовой.

   1. РАЗМЕР САМОГО ЭЛЕМЕНТА (`min-width` / `min-height`) — там, где рядом
      есть свободное место: одиночные кнопки, ряды с настраиваемым зазором,
      ячейки сеток.

   2. РАСШИРИТЕЛЬ `::after` — там, где увеличение элемента ломает раскладку
      (крестик в углу попапа, крестик внутри чипа, кнопка сворачивания в углу
      карточки оргструктуры). Прозрачный прямоугольник 44×44 центрируется на
      элементе, визуальный размер остаётся прежним. Обязательных условий два:
      позиционирующий контекст у самого элемента (иначе `position: absolute`
      у псевдоэлемента отсчитается от предка и уедет) и незаменяемый тег —
      на `<input>` псевдоэлементы не отрисовываются вовсе, см. T1-бис.

   Расширитель НЕ применяется к соседним кнопкам в плотном ряду: их
   невидимые области наложились бы друг на друга, и промах стал бы не
   реже, а чаще. Для таких рядов увеличивается кнопка и разводится зазор
   (минимум 8px между соседями).

   Ниже отдельно перекрыты два существующих мобильных правила проекта,
   которые УМЕНЬШАЮТ цели: `custom.css` (скрепка и отправка в обращениях,
   34→32px) и `tour.css` (плавающая кнопка тура, 36→32px). Сами файлы
   остаются нетронутыми — слой переопределений подключён последним. */
@media (max-width: 991.98px) {

    /* ─── T1. Расширители области нажатия ────────────────────────────
       Позиционирующий контекст. У части элементов `position: absolute`
       уже задан в их собственных стилях — повтор безвреден, зато правило
       читается одним списком и не рассыпается при правках соседних
       файлов. `absolute` менять на `relative` нельзя: у крестиков попапов
       на нём держится привязка к углу. */
    .cal-popup-close,
    .rc-voters-close,
    .rc-file__remove,
    .dept-chip-x,
    .o2o-picker__chip-x,
    .search-nav .s-btn {
        position: relative;
    }

    .emp-popup-close::after,
    .admin-popup-close::after,
    .cal-popup-close::after,
    .rc-tour-close::after,
    .rc-voters-close::after,
    .rc-file__remove::after,
    .dept-chip-x::after,
    .o2o-picker__chip-x::after,
    .col-btn::after,
    .search-nav .s-btn::after {
        content: '';
        position: absolute;
        top: 50%;
        left: 50%;
        width: 44px;
        height: 44px;
        transform: translate(-50%, -50%);
        /* Фон прозрачный: элемент выглядит ровно так же, как выглядел.
           `pointer-events` не трогаем — значение по умолчанию и нужно:
           клик по псевдоэлементу засчитывается его владельцу, в этом весь
           приём. Радиус наследуется, чтобы подсветка по нажатию у круглых
           кнопок не выезжала прямоугольником. */
        background: transparent;
        border-radius: inherit;
    }

    /* ─── T1-бис. Чекбоксы ───────────────────────────────────────────
       `<input>` — заменяемый элемент, псевдоэлементы на нём не
       отрисовываются, поэтому приём T1 к чекбоксам неприменим в
       принципе. Цель им даёт подпись: `<label for>` переключает
       связанный чекбокс, и достаточно растянуть её по вертикали.
       Сам квадратик остаётся 13–16px — визуально ряд не меняется.

       Ряд `.form-check` при этом переводится в честный флекс. У Bootstrap
       он собран на float: `padding-left: 1.5em` у обёртки и
       `margin-left: -1.5em` у поплавка. В строке высотой 44px поплавок
       прижался бы к верхнему краю, а подпись встала бы по центру —
       квадратик и текст разъехались бы по вертикали. Флекс убирает и
       расхождение, и сам поплавок. Ряды `.form-check-inline` в проекте не
       используются (проверено по шаблонам), так что блочность не мешает. */
    .form-check {
        min-height: 44px;
        display: flex;
        align-items: center;
        gap: .5rem;
        padding-left: 0;
    }

    .form-check .form-check-input {
        float: none;
        flex-shrink: 0;
        margin-top: 0;
        margin-left: 0;
    }

    .form-check-label,
    .col-row label {
        display: flex;
        align-items: center;
        min-height: 44px;
    }

    /* ─── T2. Крестики закрытия попапов ──────────────────────────────
       Визуальный размер сохранён (см. T1), но иконке нужен запас, иначе
       на 375px крестик выглядит случайной точкой. */
    .emp-popup-close,
    .admin-popup-close,
    .cal-popup-close,
    .rc-tour-close,
    .rc-voters-close {
        font-size: 1.35rem;
    }

    /* ─── T3. Одиночные и разводимые кнопки ──────────────────────────
       Календарь: шапка месяца. Стрелок у мини-календаря дашборда больше нет —
       виджет главной месяцами не листается, он показывает столько месяцев
       вперёд, сколько влезло по ширине. */
    .cal-nav-btn {
        width: 44px;
        height: 44px;
    }

    /* Компонент выбора даты: стрелки месяца и ячейки дней. Сетка дней —
       `repeat(7, 1fr)`, поэтому высота ячейки задаётся напрямую. */
    .rc-dtp-cal-header button {
        width: 44px;
        height: 44px;
    }

    .rc-dtp-day {
        height: 44px;
    }

    .rc-dtp-days {
        gap: 4px;
    }

    /* Колокольчик уведомлений. */
    .notif-bell-btn {
        width: 44px;
        height: 44px;
    }

    /* Кнопки-иконки опросов: удаление вопроса и ручка перетаскивания. */
    .poll-icon-btn-danger,
    .poll-icon-btn-secondary {
        width: 44px;
        height: 44px;
    }

    /* Скрепка и отправка в обращениях. Перекрывает их уменьшение до 32px
       в мобильном медиазапросе `custom.css` (порог 768): именно эту пару
       жмут в приложении чаще всего. */
    .ticket-composer__attach,
    .ticket-composer__send {
        width: 44px;
        height: 44px;
    }

    /* Кнопки действий новостей. Ряд плотный (`gap: .25rem`), поэтому
       вместе с размером разводится и зазор, а сам ряд получает перенос —
       четыре кнопки по 44px в строку на 320px не помещаются. */
    .news-actions {
        gap: .5rem;
        flex-wrap: wrap;
    }

    .news-action {
        width: 44px;
        height: 44px;
    }

    /* Кнопки «Изменить» и «Удалить» у события в окне дня. В calendar.css у
       них кегль .7rem и поля .22rem — цель выходила ниже 30px, а зазор ряда
       (`gap: .4rem`, 6.4px) не дотягивал до принятых в проекте 8px. Ряд
       текстовый, поэтому поднимается высота и горизонтальные поля, а не
       сторона квадрата: подписи должны остаться читаемыми. */
    .cal-event-actions {
        gap: .5rem;
    }

    .cal-event-actions button {
        min-height: 44px;
        padding: .5rem .85rem;
        font-size: .8rem;
    }

    /* «Отмена» и «Сохранить» в форме события. Общую кнопку «Закрыть» это
       окно не получает: своя «Отмена» точнее по смыслу и есть на любой
       ширине (modal-policy.js, OWN_CLOSER_SELECTORS). Раз она осталась
       единственным пальцевым выходом, её высота доводится до того же
       минимума, что у дописываемой кнопки. */
    .cal-form-actions button {
        min-height: 44px;
    }

    /* Чипы удаления в опросах: сам чип поднимается до 44px по высоте,
       крестик внутри остаётся 16px и получает расширитель (T1). Поля
       чипа разводят соседние чипы: `gap` контейнера мал (5.6px). */
    .dept-chip,
    .o2o-picker__chip {
        min-height: 44px;
        margin: 3px;
    }

    /* ─── T4. Пункты меню сайдбара ───────────────────────────────────
       Шторка — основной способ навигации на телефоне, а пункт меню там
       был самой мелкой целью каркаса (34px). */
    .nav-link-item {
        min-height: 44px;
    }

    /* ─── T5. Таблица сотрудников ────────────────────────────────────
       Кнопки страниц. Ряд собран из `d-flex gap-1` (4px) прямо в
       шаблоне, поэтому недостающие 4px зазора добираются полями самой
       кнопки — так правило не зависит от классов обёртки. */
    .page-btn {
        min-width: 44px;
        height: 44px;
        margin: 2px;
    }

    /* Ряды выбора колонок: цель — вся строка (`label` растянут на неё),
       чекбокс 13px остаётся как есть и получает расширитель (T1). */
    .col-picker-panel .col-row {
        min-height: 44px;
    }

    .col-row .drag-handle {
        min-height: 44px;
    }

    /* Пятнадцать строк по 44px — 660px, что выше половины экрана
       телефона. Панель получает собственную прокрутку. Домашняя черта
       вычитается отдельно: панель раскрывается вниз от кнопки. */
    .col-picker-panel {
        max-height: calc(100dvh - 12rem - var(--safe-bottom));
        overflow-y: auto;
    }

    /* ─── T6. Оргструктура ───────────────────────────────────────────
       Кнопки зума стоят обособленно в правом нижнем углу холста —
       увеличиваются напрямую. Кнопка сворачивания ветки сидит в углу
       карточки и увеличиться не может: расширитель (T1). */
    .z-btn {
        width: 44px;
        height: 44px;
    }

    .zoom-ctrl {
        gap: .5rem;
    }

    .search-nav {
        gap: .5rem;
    }

    /* ─── T7. Excel-фильтры столбцов ─────────────────────────────────
       Цель — строка целиком (`cursor: pointer`), а не чекбокс 16px. */
    .rc-cf__row {
        min-height: 44px;
    }

    .rc-cf__list {
        gap: 4px;
    }

    /* ─── T8. Строка фильтров журнала действий ───────────────────────
       Все контролы там выровнены по общей высоте 31px (селекты, поля дат,
       кнопки «Применить» и «Сбросить»). Высота поднимается всей группой,
       иначе ряд развалится по нижней линии. */
    .al-filters .form-select-sm,
    .al-filters .form-control-sm,
    .al-filters .rc-dtp-field,
    .al-filters .al-btn {
        height: 44px;
    }

    /* ─── T9. Кнопка удаления файла ──────────────────────────────────
       Плитка и строка списка вложений — визуальный размер прежний,
       область нажатия расширена (T1). Зазор до соседнего текста уже
       больше 8px за счёт `gap` строки. */
    .rc-file__item--row .rc-file__remove,
    .rc-file__item--tile .rc-file__remove {
        z-index: 1;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   НИЖЕ 992px — ПОЛЯ ВВОДА: 16px ПРОТИВ АВТОЗУМА iOS
   ═══════════════════════════════════════════════════════════════════

   Safari на iOS увеличивает страницу при фокусе на поле со шрифтом мельче
   16px. Само по себе это лечится обратным жестом, но каркас портала живёт
   в `height: 100dvh` при `overflow: hidden` у body — после автозума
   страница остаётся в увеличенном масштабе, и вернуть её нечем.

   Запрещать масштабирование (`user-scalable=no`) нельзя: это отбирает зум
   у тех, кому он нужен, и за этим следит tests/test_mobile_shell.py.
   Единственное корректное лечение — не давать повода, то есть 16px.

   Селектор по типу, а не по классу, намеренно: специфичность
   `input:not([type]):not([type]):not([type])` равна (0,3,1) и перекрывает
   и `.form-control-sm` (0,1,0), и `.glass-card .form-control` (0,2,0), и
   `.cal-form input[type="text"]` (0,2,1) — то есть все восемь групп полей
   из аудита разом, без перечисления и без `!important`.

   Чекбоксы, радиокнопки и ползунки исключены: шрифт их не увеличивает,
   зато `.form-check-input` у Bootstrap меряется в `em` и поехал бы
   размером. Их область нажатия закрыта расширителями выше (T1).

   Визуальный размер полей выше 992px не меняется — весь блок в @media. */
@media (max-width: 991.98px) {

    input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
    select,
    textarea {
        font-size: 16px;
    }

    /* Обёртка брендового датапикера: поле внутри — `font: inherit`
       (rc-datetime-picker.css), поэтому размер задаётся паре, иначе
       рамка не совпадёт с текстом. */
    .rc-dtp-field,
    .rc-dtp-date-display {
        font-size: 16px;
    }

    /* Форма события в календаре. Общее правило выше проходит по типу
       элемента, и его специфичность (0,3,1) перебивает
       `.cal-form input[type="text"]` (0,2,1). А вот голые `select` и
       `textarea` в том правиле — (0,0,1), и `.cal-form select` (0,1,1) их
       перебивает: список типов события и описание оставались 14.08px.

       Правило стояло в разделе календаря ниже 768px, хотя причина у него
       общая с этим слоем — автозум при фокусе. На планшете в портрете и
       телефоне в ландшафте оба поля зумили страницу. */
    .cal-form select,
    .cal-form textarea {
        font-size: 16px;
    }

    /* Диапазон дат в отчёте по обращениям. Инлайновый `font-size` заменён
       классом в шаблоне (из CSS инлайн не перебить без `!important`);
       ширина 130px тоже снимается — на 320px два поля в ряд не встают. */
    .rep-date-input {
        width: auto;
        min-width: 0;
        flex: 1 1 8rem;
    }

    /* Строка фильтров журнала действий: тот же приём с классом вместо
       инлайна. Базовый кегль ряда поднят, чтобы подписи не оставались
       12.5px рядом с полями по 16px. */
    .al-filters__row {
        font-size: .95rem;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   НИЖЕ 992px — ПЕРЕТАСКИВАНИЕ
   ═══════════════════════════════════════════════════════════════════

   Все четыре интерфейса перетаскивания переведены на SortableJS
   (app/static/js/vendor/sortable.min.js): нативные HTML5 drag-события
   мобильные Safari и Chrome не генерируют вовсе.

   Здесь остаётся визуальная часть. Ручки перетаскивания в проекте
   проявляются по `:hover` (`opacity: 0` → `1` в custom.css, изменение
   цвета в шаблоне таблицы сотрудников). На тач-устройстве наведения нет,
   и ручка не видна никогда — функция выглядит несуществующей. */
@media (max-width: 991.98px) {

    /* Ручка строки в выборе колонок таблицы сотрудников — единственная
       точка захвата строки (`handle: '.drag-handle'` в employees.html),
       и на сенсоре её надо видеть без наведения.

       Видимую ширину задаёт блок ниже, здесь остаётся расширитель (T1),
       добирающий область нажатия до 44×44. Больший запас брать нельзя:
       невидимая область наехала бы на подпись колонки, и жест по ней
       перестал бы переключать видимость. */
    .col-row .drag-handle {
        opacity: 1;
        position: relative;
        width: 32px;
        min-height: 44px;
    }

    .col-row .drag-handle::after {
        content: '';
        position: absolute;
        top: 50%;
        left: 50%;
        width: 44px;
        height: 44px;
        transform: translate(-50%, -50%);
        background: transparent;
    }

    /* Долгое нажатие по ссылке пункта меню на iOS открывает системное
       превью и перебивает жест перетаскивания. */
    .nav-reorderable .nav-link-item {
        -webkit-touch-callout: none;
    }

    /* Ручка строки в выборе колонок таблицы сотрудников: там она видна не
       по `opacity`, а по цвету, и панель узкая — своя ширина не нужна. */
    .col-row .drag-handle {
        color: rgba(255, 255, 255, .45);
        width: 28px;
    }

    [data-theme="light"] .col-row .drag-handle {
        color: rgba(0, 0, 0, .45);
    }

    /* Ручка строки статуса идей — единственная точка захвата в ряду,
       поэтому размер даётся ей самой, а не расширителем. */
    .is-handle {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-width: 44px;
        min-height: 44px;
    }

    /* Перетаскиваемый элемент под пальцем и его «дырка» в списке.
       Классы задаются при инициализации SortableJS в шаблонах. */
    .sortable-drag {
        opacity: 1;
    }

    .sortable-ghost {
        opacity: .35;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   НИЖЕ 992px — ПРОКРУТКА ВЛОЖЕННЫХ ОБЛАСТЕЙ
   ═══════════════════════════════════════════════════════════════════

   `overscroll-behavior: contain` останавливает цепочку прокрутки на
   границе области: докрутив ленту переписки до конца, пользователь не
   утаскивает следом всю страницу и не вызывает «оттягивание» (bounce)
   у корня документа.

   Правила адресные — по уже существующим прокручиваемым областям
   проекта. Глобального запрета здесь нет намеренно: `touch-action: none`
   на общем предке отключил бы и прокрутку, и масштабирование двумя
   пальцами. Единственное место с `touch-action: none` в файле —
   затемнение под шторкой навигации, где неподвижность и требуется.

   Горизонтально прокручиваемым областям (таблицы) задаётся
   `touch-action: pan-x pan-y`: жест по диагонали не блокируется, а
   браузер знает, что перехватывать его для навигации «назад» не нужно. */
@media (max-width: 991.98px) {

    /* Основная область содержимого и её аналог на странице сотрудников. */
    .page-content,
    .emp-page-content {
        overscroll-behavior-y: contain;
    }

    /* Прокрутка самой шторки навигации. */
    .app-sidebar {
        overscroll-behavior: contain;
    }

    /* Лента переписки, список вложений и панель файлов обращения. */
    .ticket-chat #ticketFeed,
    .ticket-files__list,
    .ticket-composer__files {
        overscroll-behavior: contain;
    }

    /* Полоса таблицы сотрудников: обе оси, поэтому и жест обеих осей. */
    .table-scroll {
        overscroll-behavior: contain;
        touch-action: pan-x pan-y;
    }

    /* Выпадающие панели со своим списком: выбор колонок, значения фильтра
       столбца, колонки часов и минут в выборе даты. */
    .col-picker-panel,
    .rc-cf__list,
    .rc-dtp-time-list {
        overscroll-behavior: contain;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   НИЖЕ 768px — ВЫБОР ДАТЫ И ВРЕМЕНИ
   ═══════════════════════════════════════════════════════════════════

   Компонент был недоступен с телефона целиком. Попап шириной 580px
   ужимался до ширины экрана, но внутри лежал горизонтальный ряд без
   переноса: календарь (минимум 280px) плюс разделитель плюс панель времени
   (220px) плюс отступы — минимум 565px. Панель часов и минут физически
   оказывалась за границей бокса. Нативное поле компонент прячет, а даты
   опроса обязательны — создать или отредактировать опрос было нельзя.

   Здесь попап перестаёт быть выпадашкой у поля и раскладывается как
   модальное окно: календарь сверху, время под ним, вся конструкция по
   центру экрана. Привязку к полю снимает сам компонент — ниже 768px
   rc-datetime-picker.js не пишет инлайновые координаты (тот же порог
   объявлен там константой MOBILE_MQ). Позиционирование выше 768px
   компонент считает по-прежнему, но теперь пересчитывает при прокрутке
   любого контейнера и прижимает попап к границе экрана, когда тот не
   помещается ни сверху, ни снизу целиком. */
@media (max-width: 767.98px) {

    /* ─── D1. Ряд полей-триггеров ────────────────────────────────────
       Дата (минимум 180px) и время (100px) с зазором дают 288px и в
       колонке модалки на 320px уже не встают в строку. */
    .rc-dtp-row {
        max-width: none;
        flex-wrap: wrap;
    }

    .rc-dtp-date {
        min-width: 0;
        flex: 1 1 100%;
    }

    .rc-dtp-time {
        flex: 1 1 100%;
    }

    .rc-dtp-hint {
        max-width: none;
    }

    /* ─── D2. Попап как модальное окно ───────────────────────────────
       `position: fixed` вместо `absolute`: попап лежит в body, а поле
       может жить внутри своего скролл-контейнера (тело bootstrap-модалки),
       и абсолютная привязка к странице там ничего не значит. Оба края
       заданы явно — ширина считается от них, поэтому и 580px базового
       правила, и 340px date-режима перестают действовать. */
    .rc-dtp-popup {
        position: fixed;
        top: 50%;
        left: calc(12px + var(--safe-left));
        right: calc(12px + var(--safe-right));
        width: auto;
        max-width: none;
        transform: translateY(-50%);

        flex-direction: column;
        gap: 12px;
        /* Нижние 60px базового отступа держали абсолютный футер — ниже он
           встаёт в поток, и запас больше не нужен. */
        padding: 16px;

        /* Высокий попап (календарь + панель времени + футер) занимает почти
           весь экран, поэтому в вычитаемое входят и безопасные зоны: иначе в
           режиме приложения верх календаря уходил под вырез, а кнопки
           футера — под домашнюю черту. */
        max-height: calc(100dvh - 24px - var(--safe-top) - var(--safe-bottom));
        overflow-y: auto;
        overscroll-behavior: contain;
    }

    /* Минимум 280px у календаря — вторая половина причины переполнения:
       на 320px внутри отступов остаётся 264px. */
    .rc-dtp-calendar {
        min-width: 0;
        flex: 0 0 auto;
    }

    /* Разделитель из вертикальной полосы становится горизонтальной. */
    .rc-dtp-divider {
        width: auto;
        height: 1px;
        align-self: stretch;
    }

    /* `flex: 0 0 220px` в колонке задавал бы уже не ширину, а высоту. */
    .rc-dtp-time-panel {
        flex: 0 0 auto;
    }

    /* Колонки часов и минут получают вертикальный запас: по высоте места
       теперь достаточно, а элемент списка поднят до пальцевого размера. */
    .rc-dtp-time-columns {
        height: 200px;
    }

    .rc-dtp-time-item {
        flex: 0 0 44px;
    }

    /* ─── D3. Футер попапа ───────────────────────────────────────────
       Был прибит к низу через `position: absolute`; в прокручиваемом
       окне он наезжал бы на содержимое. */
    .rc-dtp-footer {
        position: static;
        left: auto;
        right: auto;
        bottom: auto;
        gap: 8px;
    }

    .rc-dtp-clear,
    .rc-dtp-today,
    .rc-dtp-done {
        min-height: 44px;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   НИЖЕ 992px — ЗАКРЫТИЕ ОКОН ПАЛЬЦЕМ
   ═══════════════════════════════════════════════════════════════════

   Глобальная политика проекта гасит клик по подложке в фазе перехвата, а
   bootstrap-модалкам принудительно ставит статичную подложку. Решение
   сознательное (модалку слишком легко закрыть случайным промахом) и здесь
   не отменяется. Но выход из окна на десктопе даёт Esc, которого на
   сенсорном экране нет, и единственной целью остаётся крестик 20–30px
   в углу.

   Порог — 992px, а не 768px: это момент, когда сайдбар уезжает за левый
   край и портал переходит в сенсорную раскладку. Клавиатуры с этого места
   уже нет, значит нет и Esc. На 768–992 (планшет в портрете, телефон в
   ландшафте) окна оставались вовсе без пальцевого выхода.

   Поэтому у окон появляется явная кнопка «Закрыть» во всю ширину нижней
   части. Механизм общий, шаблоны разделов не правятся:
     · у bootstrap-модалок футер уже есть — он раскладывается в столбец,
       и кнопка отмены оказывается внизу на всю ширину;
     · окнам без футера кнопку дописывает modal-policy.js (класс
       `.rc-mclose`, тот же порог константой MOBILE_MQ), нажатие
       транслируется их собственному крестику. */
@media (max-width: 991.98px) {

    .rc-mclose {
        display: block;
        width: 100%;
        min-height: 48px;
        margin-top: 1rem;
        padding: .7rem 1rem;
        border: 1px solid rgba(217, 225, 255, .18);
        border-radius: 12px;
        background: rgba(217, 225, 255, .08);
        color: inherit;
        font: inherit;
        font-weight: 600;
        line-height: 1.2;
        cursor: pointer;
    }

    [data-theme="light"] .rc-mclose {
        border-color: rgba(0, 0, 0, .12);
        background: rgba(0, 0, 0, .05);
    }

    /* Посадка кнопки в попапе дня календаря. Стоит здесь, а не в разделе
       календаря: правило работает ровно тогда, когда кнопка вообще есть,
       и разъехаться пороги не должны. Попап — флекс-колонка с
       `overflow: hidden`, и без `flex-shrink: 0` кнопка ужалась бы в ноль
       вместе с телом, а без полей упёрлась бы в края окна. */
    .cal-popup > .rc-mclose {
        flex-shrink: 0;
        width: auto;
        margin: 0 1.2rem 1.1rem;
    }

    /* Футер bootstrap-модалки в столбец. `column-reverse` намеренно:
       в разметке отмена стоит первой, а внизу — под большим пальцем —
       нужна именно она. Отступы у кнопок бутстраповские (`margin: .25rem`),
       поэтому зазор задаётся `gap`, а поля снимаются. */
    .modal-footer {
        flex-direction: column-reverse;
        align-items: stretch;
        gap: .5rem;
    }

    .modal-footer > *,
    .modal-footer > form > button {
        width: 100%;
        margin: 0;
        min-height: 44px;
    }

    /* Диалог подтверждения: кнопки «Отмена» и «Да» стояли в ряд справа
       высотой 37px. Порядок тот же — отмена внизу. */
    .rc-confirm-actions {
        flex-direction: column-reverse;
        gap: .5rem;
    }

    .rc-confirm-btn {
        width: 100%;
        min-height: 44px;
    }
}

/* Кнопка дописывается только ниже 992px, но из разметки уже не убирается:
   если окно потом растянули до широкого, показывать её незачем — там есть
   и Esc, и крестик обычного размера.

   Порог зеркальный порогу блока выше и не пересекается с ним: 991.98px
   против 992px. Ровно на 992px работает только это правило — там сайдбар
   уже в потоке. */
@media (min-width: 992px) {
    .rc-mclose {
        display: none;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   НИЖЕ 768px — ПОПАПЫ ШИРЕ ЭКРАНА
   ═══════════════════════════════════════════════════════════════════

   Жёсткие минимальные ширины перебивают и `max-width`, и `width` в
   процентах: окно становится шире экрана, а крестик в его правом верхнем
   углу уезжает за кромку. Исходные правила живут в custom.css и в шаблоне
   вкладки синхронизации — здесь они только перекрываются.

   Верхняя граница ширины у каждого окна своя и сохраняется: на планшете
   в 760px растягивать карточку сотрудника на весь экран незачем. */
@media (max-width: 767.98px) {

    /* Карточка сотрудника в оргструктуре: `min-width: 340px` при
       `max-width: 420px`. На 320px окно шире экрана на 20px. */
    .emp-popup {
        min-width: 0;
        width: min(420px, calc(100vw - 24px));
    }

    /* Скрытый вход администратора: `min-width: 340px` перебивал
       собственную же `width: 90vw`. */
    .admin-login-popup {
        min-width: 0;
        width: min(420px, calc(100vw - 24px));
    }

    /* Детали изменений на вкладке синхронизации: `min-width: 480px`.
       Высота тоже пересчитывается на видимый вьюпорт — базовые 80vh на
       телефоне считаются от высоты БЕЗ адресной строки. Безопасные зоны
       входят в вычитаемое: в режиме приложения окно во весь экран упиралось
       бы в вырез сверху и домашнюю черту снизу. */
    .ch-dialog {
        min-width: 0;
        width: min(680px, calc(100vw - 24px));
        max-height: calc(100dvh - 24px - var(--safe-top) - var(--safe-bottom));
    }

    /* Панель фильтра столбца. Ширина 280px в экран помещается (проверено
       по коду: собственный `max-width: calc(100vw - 24px)` уже стоит), а
       вот высота — нет: сортировка, поиск, массовые кнопки, список на
       240px, подсказка и футер дают около 460px. Панель получает свою
       прокрутку, чтобы кнопка «Применить» оставалась достижимой. */
    .rc-cf__panel {
        min-width: 0;
        max-height: calc(100dvh - 8rem - var(--safe-bottom));
        overflow-y: auto;
        overscroll-behavior: contain;
    }

    /* Список проголосовавших и диалог подтверждения по ширине проходят
       (`width: 90%`), а высота у первого задана в vh — тот же пересчёт. */
    .rc-voters-dialog {
        max-height: calc(100dvh - 24px - var(--safe-top) - var(--safe-bottom));
    }

    /* Строка списка вложений: `min-width: 220px` в теле модалки на 320px
       подпирает границы. */
    .rc-file__item--row {
        min-width: 0;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   НИЖЕ 992px — ОБУЧАЮЩИЙ ТУР
   ═══════════════════════════════════════════════════════════════════

   Тур на телефоне не запускался визуально вовсе. Подсказку без привязки к
   элементу JS центрировал инлайновой парой `left/top: 50%` вместе со
   смещением на половину собственных размеров, а мобильное правило в tour.css
   перебивало горизонтальную координату с пометкой `!important`. Смещение при
   этом оставалось — подсказку уносило влево на половину ширины, и первый
   шаг вместе со стартовым экраном оказывались за краем экрана. Подсказка с
   привязкой жёстко вставала под подсвеченный элемент без проверки, помещается
   ли она по высоте; для шагов, указывающих в низ сайдбара, кнопки уходили
   вниз за экран, а затемняющий слой перехватывал нажатия. Первый вход с
   телефона встречал пользователя полноэкранным затемнением без выхода.

   Здесь подсказка перестаёт быть привязанной к элементу и раскладывается
   шторкой у нижнего края: оба края и низ заданы явно, координаты целиком за
   CSS. Инлайновые значения снимает сам тур — ниже порога он их не пишет (тот
   же порог объявлен в tour.js константой MOBILE_MQ), поэтому `!important`
   больше не нужен и в tour.css его нет. Подсветка элемента сохраняется; если
   он попадает под шторку, тур подкручивает страницу так, чтобы он оказался
   в верхней части экрана.

   Порог — 992px, а не 768px. Он выбран по сайдбару: ниже 992px тот уезжает
   за левый край и помечается inert, а три шага тура наведены внутрь него.
   Формат подсказки от того же порога и зависит — в мобильном формате такие
   шаги переводятся на бургер, а админские выпадают. Разведи эти два порога —
   и в полосе 768–992 подсветка снова показывала бы на пустое место.

   Полосу закрывает и вторая причина. Десктопная раскладка кламписа только
   по горизонтали: не поместившись ни сверху, ни снизу, подсказка всё равно
   ставится под элемент и уходит за нижний край. В полосе живёт телефон в
   ландшафте (932×430 и 852×430) — там высоты ровно столько, чтобы это
   воспроизводилось. Шторка от высоты экрана не зависит вовсе.

   Кнопки навигации — ряд равных долей с переносом: на 375px три кнопки по
   6rem встают в строку, на 320px переносятся. Высота 48px — выше принятого
   в проекте минимума 44px, потому что это единственный способ уйти из тура.
   ═══════════════════════════════════════════════════════════════════ */
@media (max-width: 991.98px) {

    /* ─── U1. Шторка у нижнего края ──────────────────────────────────
       `top: auto` обязателен: подсказка могла прийти сюда с десктопной
       раскладки, где верх задан правилом, а не только инлайном.

       Собственная прокрутка отдаётся не всей шторке, а её текстовому блоку
       (U3): у прокручиваемой целиком шторки кнопки уехали бы вместе с
       текстом, а из тура нужно уметь выйти на любом шаге. */
    .rc-tour-tooltip.rc-tour-sheet {
        top: auto;
        left: calc(12px + var(--safe-left));
        right: calc(12px + var(--safe-right));
        bottom: calc(12px + var(--safe-bottom));
        width: auto;
        max-width: none;

        display: flex;
        flex-direction: column;
        max-height: min(72dvh, calc(100dvh - 24px));
        overflow: hidden;

        border-radius: 16px;
        padding: 1.1rem 1.1rem .9rem;

        /* Появление снизу вверх вместо масштабирования от центра: шторка
           выезжает от края, к которому прибита. Селектор с двумя классами
           перебивает `transform: scale(.92)` базового правила, с тремя
           (ниже) — `scale(1)` видимого состояния. */
        transform: translateY(16px);
    }

    .rc-tour-tooltip.rc-tour-sheet.rc-tour-visible {
        transform: none;
    }

    /* ─── U2. Заголовок ──────────────────────────────────────────────
       Отступ справа — под крестик: он `position: absolute` в углу и на
       узкой шторке налезал бы на длинный заголовок. */
    .rc-tour-sheet .rc-tour-tooltip-title {
        flex-shrink: 0;
        padding-right: 2.5rem;
        font-size: 1rem;
    }

    .rc-tour-sheet .rc-tour-close {
        top: .6rem;
        right: .6rem;
    }

    /* ─── U3. Текст ──────────────────────────────────────────────────
       `min-height: 0` обязателен: у флекс-элемента минимальная высота по
       умолчанию равна содержимому, и блок не дал бы себя ужать — шторка
       выросла бы выше max-height, а прокрутка внутри не появилась. */
    .rc-tour-sheet .rc-tour-tooltip-text,
    .rc-tour-sheet .rc-tour-new-list {
        flex: 1 1 auto;
        min-height: 0;
        overflow-y: auto;
        overscroll-behavior: contain;
        font-size: .9rem;
    }

    /* ─── U4. Кнопки навигации ───────────────────────────────────────
       Ряд равных долей; `flex-shrink: 0` держит его на месте при прокрутке
       текста. Прогресс «3 / 11» вынесен на свою строку — иначе он съедал
       бы долю ряда наравне с кнопками. */
    .rc-tour-sheet .rc-tour-tooltip-footer {
        flex-shrink: 0;
        flex-wrap: wrap;
        gap: .5rem;
        margin-top: .2rem;
    }

    .rc-tour-sheet .rc-tour-progress {
        flex: 1 0 100%;
        font-size: .75rem;
    }

    .rc-tour-sheet .rc-tour-btn,
    .rc-tour-sheet .rc-tour-btn-skip {
        flex: 1 1 6rem;
        min-height: 48px;
        padding: .6rem .5rem;
        border-radius: 12px;
        font-size: .9rem;
        font-weight: 600;
    }

    /* «Пропустить» в шторке — такая же кнопка, а не текстовая ссылка
       мелким кеглем. `margin-right` снимается: на стартовом экране
       tour.css отжимает её влево через `auto`, и в ряду равных долей это
       ломало бы раскладку. */
    .rc-tour-sheet .rc-tour-btn-skip {
        margin-right: 0;
        border: 1px solid rgba(255, 255, 255, .16);
        background: rgba(255, 255, 255, .04);
        color: rgba(255, 255, 255, .6);
    }

    [data-theme="light"] .rc-tour-sheet .rc-tour-btn-skip {
        border-color: rgba(0, 0, 0, .12);
        background: rgba(0, 0, 0, .05);
        color: rgba(0, 0, 0, .55);
    }

    /* ─── U5. Стрелка-указатель ──────────────────────────────────────
       Указывать ей больше некуда: шторка стоит у нижнего края, а не у
       подсвеченного элемента. Раньше её смещение считалось от координаты,
       перебитой `!important`, и стрелка показывала мимо. */
    .rc-tour-sheet .rc-tour-arrow {
        display: none;
    }
}

/* ─── U6. Ширина шторки на планшете ──────────────────────────────────
   Шторка растянута от края до края — на телефоне это и есть вся ширина.
   В полосе 768–992 её края разошлись бы на 968px, и строка текста стала
   бы вчетверо длиннее читаемой. Ширина ограничивается, а поля `auto`
   ставят шторку по центру между теми же краями.

   Запрос сдвоенный намеренно: ниже 768px ограничение не должно включаться
   вовсе — иначе на телефоне в ландшафте (667px) шторка перестала бы
   доходить до краёв, а это её исходная раскладка. */
@media (min-width: 768px) and (max-width: 991.98px) {
    .rc-tour-tooltip.rc-tour-sheet {
        max-width: 560px;
        margin-inline: auto;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   НИЖЕ 768px — КАЛЕНДАРЬ
   ═══════════════════════════════════════════════════════════════════

   До этого пакета ни одно мобильное правило не доходило до страницы
   /calendar: все четыре медиазапроса в calendar.css относились только к
   виджету главной (`.cal-dash*`). Сетка месяца, шапка, попап дня и форма
   события жили в десктопных размерах.

   Ширину сетке вернул пакет каркаса — после сворачивания сайдбара под неё
   есть вся ширина экрана, а не 143px. Осталось распорядиться этой шириной:
   поле страницы и поля карточки на телефоне забирали 88px из 320, а зазор
   4px — ещё 24px. Здесь и то, и другое ужимается, а колонки переводятся на
   `minmax(0, 1fr)`: с `1fr` минимальный размер ячейки равен её содержимому,
   и сетка могла вылезти за карточку вместо того, чтобы ужаться.

   Про пару `aspect-ratio: 1/1` + `min-height: 44px` у ячейки. Это НЕ
   недосмотр: при ширине ячейки больше 44px работает соотношение сторон и
   ячейка квадратная, при меньшей — минимум по высоте. Семь колонок по 44px
   на экране 320px не помещаются физически (308px против 254px доступных),
   поэтому пальцевой размер держит именно высота ряда, а ширина берётся
   максимально возможная: 36px на 320px, 44px на 375px, 51px на 430px.
   ═══════════════════════════════════════════════════════════════════ */
@media (max-width: 767.98px) {

    /* ─── L1. Поле страницы и карточка ───────────────────────────────
       Те же отступы, что у `.page-content` выше: страница календаря
       выбивалась из общего правила только из-за инлайнового стиля,
       который вынесен в класс. Поля карточки ужаты сильнее — на телефоне
       рамка вокруг сетки ценнее, чем воздух по краям. */
    .cal-page-content {
        padding: 1rem;
        padding-bottom: calc(1rem + var(--safe-bottom));
    }

    .cal-wrap {
        padding: .75rem .5rem 1rem;
        border-radius: 12px;
    }

    /* ─── L2. Шапка ──────────────────────────────────────────────────
       Ряд «‹ месяц › + Событие» не переносился: две кнопки по 44px
       (T3 выше), заголовок и кнопка создания ~100px в строку на 320px не
       встают. Кнопка создания уходит на свою строку во всю ширину —
       порядок в разметке уже такой, достаточно разрешить перенос и
       задать ей полную долю. */
    .cal-header {
        flex-wrap: wrap;
        gap: .4rem;
        margin-bottom: .75rem;
    }

    .cal-title {
        min-width: 0;
        font-size: .95rem;
    }

    .cal-add-btn {
        flex: 1 0 100%;
        justify-content: center;
        min-height: 44px;
        font-size: .85rem;
    }

    /* ─── L3. Сетка месяца ───────────────────────────────────────────
       `minmax(0, 1fr)` вместо `1fr` — единственная строка, которая
       гарантирует, что сетка не шире карточки. */
    .cal-grid {
        grid-template-columns: repeat(7, minmax(0, 1fr));
        gap: 3px;
    }

    .cal-day {
        padding: .25rem;
        border-radius: 6px;
        /* Повтор базового значения намеренно: цель пальца по вертикали
           задаётся здесь же, где считается ширина ячейки. */
        min-height: 44px;
    }

    /* Число дня: 12.8px в ячейке 36px читалось на пределе. */
    .cal-day-num {
        font-size: .9rem;
    }

    .cal-dow {
        font-size: .62rem;
        letter-spacing: .02em;
        padding: .2rem 0 .3rem;
    }

    /* ─── L4. Пометка о дне рождения ─────────────────────────────────
       Аватар 24px с рамкой 2px стоит `position: absolute` в правом
       верхнем углу и в ячейке 36px полностью накрывает число. Уменьшать
       его некуда: размер, при котором он расходится с числом, — это
       12–14px, где от лица и от инициалов не остаётся ничего, кроме
       цветного пятна. Такое пятно у ячейки уже есть — точка `.cal-dot`
       внизу по центру, плюс нижняя цветная полоса самой ячейки. Поэтому
       на телефоне остаётся точка (чуть крупнее), а фото и имя показывает
       попап дня — там аватар выводится в полном размере. */
    .cal-cell-birthday-photo {
        display: none;
    }

    .cal-dot {
        width: 7px;
        height: 7px;
    }

    .cal-dots {
        bottom: 3px;
        gap: 2px;
    }

    .cal-legend {
        gap: .4rem 1rem;
        margin-top: .75rem;
        font-size: .7rem;
    }

    /* ─── L5. Попап дня ──────────────────────────────────────────────
       86vh базового правила на телефоне считается от высоты БЕЗ адресной
       строки — тот же пересчёт на видимый вьюпорт, что у остальных окон
       проекта.

       Посадка дописываемой кнопки «Закрыть» — не здесь: она включается
       вместе с самой кнопкой, ниже 992px (раздел закрытия окон пальцем). */
    .cal-popup {
        max-height: calc(100dvh - 24px - var(--safe-top) - var(--safe-bottom));
    }

    /* ─── L6. Мини-календарь главной ─────────────────────────────────
       Сетка задана жёстко: `repeat(7, 28px)` плюс `gap: 3px` — 214px
       независимо от экрана, а ячейке ширина и высота прописаны прямо.
       Явная ширина у элемента сетки сильнее размера колонки, поэтому
       снимать нужно и её, и высоту, иначе `aspect-ratio` не применится.

       Блок месяца — элемент флекс-контейнера с базой по содержимому;
       без `flex-grow` колонки `1fr` посчитались бы от max-content, и
       сетка осталась бы прежней ширины. */
    .cal-dash-month-block {
        flex: 1 1 auto;
    }

    .cal-dash-grid {
        grid-template-columns: repeat(7, minmax(0, 1fr));
        justify-content: stretch;
    }

    .cal-dash-cell {
        width: auto;
        height: auto;
        aspect-ratio: 1 / 1;
        min-height: 44px;
        font-size: .78rem;
    }

}


/* ═══════════════════════════════════════════════════════════════════
   ОРГСТРУКТУРА
   ═══════════════════════════════════════════════════════════════════

   Два диапазона. Ниже 992px — шапка страницы: она приводится к общему виду
   портала вместе с остальными (см. H2 в каркасе). Ниже 768px — само дерево.

   Дерево на десктопе — абсолютно спозиционированные карточки на холсте с
   масштабированием, панорамированием и SVG-соединителями. На узком экране
   оно непригодно при любом масштабе: карточка 290px шире экрана, а в
   масштабе, где она помещается, подписи становятся 5–7px. Поэтому ниже
   768px холст не показывается и не инициализируется вовсе, а вместо него
   рисуется списочное дерево (`renderList()` в шаблоне страницы). Порог
   объявлен там же константой LIST_MQ — разойдись они, представление и
   раскладка оказались бы из разных диапазонов.

   Разметку строк отдаёт скрипт, здесь только раскладка. Ширина строки —
   по экрану: у текстовых блоков `min-width: 0`, у имени перенос, у
   должности и отдела многоточие, поэтому горизонтальной прокрутки нет ни
   при какой глубине вложенности.
   ═══════════════════════════════════════════════════════════════════ */
/* ─── R1. Шапка страницы ─────────────────────────────────────────────
   Своей шапки у оргструктуры больше нет: раздел перешёл на общий блок
   (_page_header.html), и вся геометрия приходит из правила H2 каркаса.
   Здесь осталась только подсказка по управлению деревом — она переехала из
   шапки под неё и на телефоне занимает свою строку. */
@media (max-width: 991.98px) {

    .chart-hint {
        padding: .5rem 1rem 0;
        font-size: .72rem;
        line-height: 1.35;
    }
}


/* Подсказка про мышиные жесты ниже 768px не нужна — холста нет; вместо неё
   показывается строка про нажатие и раскрытие ветки (разметка в шаблоне
   страницы). */
@media (max-width: 767.98px) {

    .chart-sub-mouse {
        display: none;
    }

    .chart-sub-touch {
        display: block;
        font-size: .72rem;
        line-height: 1.35;
    }

    .chart-toolbar {
        flex: 1 1 100%;
        gap: .5rem;
    }

    /* Кнопки тулбара переносились, но каждая всё равно вылезала за колонку
       и срезалась `overflow-x: clip` у `.app-main`. Теперь они делят строку
       равными долями и переносятся, когда доли меньше 8rem. */
    .tb-btn {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        flex: 1 1 8rem;
        min-height: 44px;
        font-size: .85rem;
    }

    /* Сброс вида относится к холсту, которого в этом диапазоне нет. */
    .tb-btn--canvas {
        display: none;
    }

    /* ─── R2. Поиск ──────────────────────────────────────────────────
       `flex: 0 1 200px` ужимался до ~95px, а внутренние поля поля ввода
       съедали 61px — под текст оставалось около 34px. При появлении
       совпадений блок навигации вытеснял поле в ноль. Здесь поле занимает
       свою строку целиком, а навигация по результатам — следующую.

       Кегль поля не задаётся: общее правило «16px всем полям» выше
       перекрывает `.chart-search input` по специфичности. */
    .chart-search {
        flex: 1 1 100%;
        flex-wrap: wrap;
        gap: .5rem;
    }

    .chart-search .input-wrap {
        flex: 1 1 100%;
    }

    .chart-search input {
        min-height: 44px;
        padding: .5rem 2.6rem .5rem 2.4rem;
    }

    .chart-search .search-icon {
        left: .85rem;
        font-size: .9rem;
    }

    /* Крестик очистки стоит внутри поля и увеличиться не может — область
       нажатия расширяется прозрачным псевдоэлементом (приём T1 выше).
       Соседей у него нет, накладываться не на что. */
    .chart-search .clear-btn {
        right: .8rem;
        font-size: 1rem;
    }

    .chart-search .clear-btn::after {
        content: '';
        position: absolute;
        top: 50%;
        left: 50%;
        width: 44px;
        height: 44px;
        transform: translate(-50%, -50%);
        background: transparent;
    }

    .search-nav {
        flex: 1 1 100%;
        margin-left: 0;
        gap: .5rem;
    }

    /* Здесь кнопки становятся честными 44px, а не расширителем: строка
       своя, места хватает. Расширитель из блока тач-таргетов совпадает с
       кнопкой и ничему не мешает. */
    .search-nav .s-btn {
        width: 44px;
        height: 44px;
        border-radius: 10px;
        font-size: .9rem;
    }

    .search-counter {
        flex: 1;
        min-width: 0;
        text-align: left;
        font-size: .8rem;
    }

    /* ─── R3. Списочное дерево ───────────────────────────────────────
       Прокрутка своя, как у `.page-content`: страница целиком не
       прокручивается — у `body.app-layout` стоит overflow: hidden.

       Правило с атрибутом обязательно: скрытием управляет скрипт через
       `hidden`, а объявление `display: block` ниже сильнее браузерного
       `[hidden] { display: none }` — без него список остался бы виден при
       недоступном Bootstrap (он приходит с CDN). */
    .org-list {
        display: block;
        flex: 1;
        min-height: 0;
        overflow-y: auto;
        overscroll-behavior-y: contain;
        padding: .25rem calc(.5rem + var(--safe-right))
                 calc(1rem + var(--safe-bottom)) calc(.5rem + var(--safe-left));
    }

    .org-list[hidden] {
        display: none;
    }

    /* Уровень подчинения — отступ слева. Шаг 12px и потолок в шесть
       уровней (ставится скриптом): без потолка глубокая ветка съела бы
       отступом всю ширину строки. */
    .org-row {
        display: flex;
        align-items: center;
        gap: .55rem;
        box-sizing: border-box;
        width: 100%;
        min-height: 56px;
        padding: .45rem .5rem;
        padding-left: calc(.4rem + var(--org-lvl, 0) * 12px);
        border-bottom: 1px solid rgba(217, 225, 255, .07);
        cursor: pointer;
    }

    /* Кнопка раскрытия ветки. Кроме стрелки на ней ничего нет: число
       подчинённых убрано, и вместе с ним — зазор между двумя элементами и
       начертание для цифры. Кегль поднят: единственному глифу в кнопке
       44×44 прежние .78rem оставляли слишком мелкую стрелку. */
    .org-row__toggle {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        flex-shrink: 0;
        min-width: 44px;
        min-height: 44px;
        padding: 0 .3rem;
        border: 1px solid rgba(34, 85, 238, .35);
        border-radius: 10px;
        background: rgba(34, 85, 238, .12);
        color: var(--ac, #2255EE);
        font-size: .95rem;
        line-height: 1;
        cursor: pointer;
    }

    /* У сотрудника без подчинённых кнопки нет, но её место остаётся:
       иначе аватары строк одного уровня встали бы по разным вертикалям. */
    .org-row__toggle--leaf {
        min-width: 44px;
        min-height: 0;
        border: none;
        background: none;
    }

    .org-row__photo {
        width: 36px;
        height: 36px;
        flex-shrink: 0;
        border-radius: 50%;
        object-fit: cover;
    }

    .org-row__photo--ini {
        display: flex;
        align-items: center;
        justify-content: center;
        background: linear-gradient(135deg, #000066 0%, #2255EE 100%);
        color: #fff;
        font-size: .72rem;
        font-weight: 700;
        letter-spacing: .03em;
    }

    /* `min-width: 0` — то самое, без чего флекс-элемент не даёт себя ужать
       и строка распирает список по горизонтали. */
    .org-row__info {
        flex: 1;
        min-width: 0;
    }

    .org-row__name {
        font-size: .88rem;
        font-weight: 600;
        line-height: 1.25;
        color: #e2e8f0;
        overflow-wrap: anywhere;
    }

    .org-row__title,
    .org-row__dept {
        font-size: .74rem;
        line-height: 1.3;
        color: rgba(255, 255, 255, .45);
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .org-row__dept {
        color: rgba(255, 255, 255, .32);
    }

    /* Те же три состояния, что у карточек холста: своя карточка,
       совпадение поиска и текущее совпадение. */
    .org-row--me {
        background: rgba(45, 212, 191, .18);
    }

    .org-row--hit {
        background: rgba(34, 85, 238, .12);
    }

    .org-row--active {
        background: rgba(34, 85, 238, .22);
        box-shadow: inset 3px 0 0 var(--ac, #2255EE);
    }

    /* ─── R4. Карточка сотрудника ────────────────────────────────────
       Жёсткая минимальная ширина снята пакетом общих компонентов, здесь
       доводится внутренняя раскладка: длинное «О себе» больше не режется
       по высоте молча, а само окно прокручивается внутри экрана. Кнопку
       «Закрыть» во всю ширину дописывает общий механизм (modal-policy.js:
       `.emp-popup` есть в его списке окон), переход в профиль — отдельной
       кнопкой в разметке страницы. */
    .emp-popup {
        max-height: calc(100dvh - 24px - var(--safe-top) - var(--safe-bottom));
        overflow-y: auto;
        overscroll-behavior: contain;
        padding: 1.25rem 1.1rem 1.1rem;
    }

    .emp-popup-value {
        max-height: none;
    }

    .emp-popup-profile {
        min-height: 48px;
        font-size: .95rem;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   НИЖЕ 768px — ОРГСТРУКТУРА, СВЕТЛАЯ ТЕМА
   Строки списка рисуются на тёмной палитре; на белом фоне им нужны свои
   цвета, как и остальным элементам портала в custom.css.
   ═══════════════════════════════════════════════════════════════════ */
@media (max-width: 767.98px) {

    [data-theme="light"] .org-row {
        border-bottom-color: rgba(0, 0, 0, .08);
    }

    [data-theme="light"] .org-row__name {
        color: var(--rc-black, #020A0A);
    }

    [data-theme="light"] .org-row__title {
        color: rgba(0, 0, 0, .55);
    }

    [data-theme="light"] .org-row__dept {
        color: rgba(0, 0, 0, .42);
    }

    [data-theme="light"] .org-row__toggle {
        background: color-mix(in srgb, var(--ac) 10%, transparent);
        border-color: color-mix(in srgb, var(--ac) 30%, transparent);
        color: var(--rc-blue, #0000BB);
    }

    [data-theme="light"] .org-row--me {
        background: rgba(45, 212, 191, .2);
    }

    [data-theme="light"] .org-row--hit {
        background: rgba(34, 85, 238, .08);
    }

    [data-theme="light"] .org-row--active {
        background: rgba(34, 85, 238, .14);
    }
}


/* ═══════════════════════════════════════════════════════════════════
   НИЖЕ 768px — ОПРОСЫ
   ═══════════════════════════════════════════════════════════════════

   Про плавающую панель прохождения. В аудите она числилась главным
   блокером зоны: `z-index: 100` против `z-index: 200` у сайдбара, и на
   узком экране растянутая по окну панель уезжала под сайдбар — кнопка
   «Назад» пропадала целиком. После пакета каркаса сайдбар ниже 992px
   уехал за левый край, и в исходном виде дефект не воспроизводится:
   перекрывать панель стало нечем. Остались два следа. Первый — смещение
   на половину сайдбара (`left: calc(50% + 116px)`) в базовом правиле:
   оно снималось сначала ниже 600px, потом ниже 768px, и полоса, где
   панель стояла сдвинутой вправо при отсутствующем сайдбаре, только
   сокращалась. Снимается оно ниже — правилом W0 на 991.98px, то есть
   ровно там, где сайдбар и уезжает. Второй — слои: сейчас панель выше мобильной
   шапки (z-index 25) и ниже затемнения выдвижной шторки (190) и самой
   шторки (200). Это и есть согласованное состояние: панель видна и
   нажимаема, а открытая шторка её накрывает. Ничего из этого не
   переопределяется — здесь только размеры и безопасная зона.
   ═══════════════════════════════════════════════════════════════════ */
/* ─── W0. Смещение панели прохождения на половину сайдбара ───────────
   Единственное правило раздела, которое привязано не к ширине экрана, а к
   наличию сайдбара, — поэтому у него свой порог.

   Панель `fixed`, и `left` считается от ОКНА, тогда как колонка с вопросом
   начинается за сайдбаром. Базовое правило (polls.css) прибавляет половину
   сайдбара обратно, чтобы панель встала под центром колонки. Ниже 992px
   сайдбара нет — прибавлять нечего, и панель снова центрируется по окну.

   Порог правки до этого стоял на 768px, и в полосе 768–992 панель
   оставалась сдвинутой вправо на 116px при отсутствующем сайдбаре. */
@media (max-width: 991.98px) {
    .poll-wizard-nav {
        left: 50%;
    }
}

@media (max-width: 767.98px) {

    /* ─── W1. Панель навигации прохождения ───────────────────────────
       Низ экрана телефона занят домашней чертой — панель поднимается над
       ней. Кнопки внутри уже растянуты равными долями (polls.css), здесь
       им задаётся пальцевая высота. */
    .poll-wizard-nav {
        bottom: calc(.5rem + var(--safe-bottom));
        padding: .5rem .6rem;
        gap: .5rem;
    }

    .poll-wizard-nav .btn-poll,
    .poll-wizard-nav .btn-auth {
        min-height: 44px;
        padding: .5rem .75rem;
    }

    /* Запас под панелью считался от её десктопной высоты. С кнопками по
       44px и безопасной зоной последний вопрос уходил под панель. */
    .poll-wizard-slides {
        margin-bottom: calc(6.5rem + var(--safe-bottom));
    }

    /* ─── W2. Шапка вопроса в конструкторе ───────────────────────────
       Ручка перетаскивания (44px после слоя тач-таргетов), поле текста,
       список типа (до 220px) и кнопка удаления (44px) дают в строку
       больше 350px. Строка получает перенос и раскладывается в две:
       ручка с полем вопроса и список типа с кнопкой удаления. Ручка
       остаётся в первой строке — за неё держится перетаскивание
       (SortableJS, `handle: '.q-drag-handle'`). */
    .poll-builder-q-head {
        flex-wrap: wrap;
    }

    .poll-builder-q-head .q-text {
        flex: 1 1 8rem;
        min-width: 0;
    }

    .poll-builder-q-head .q-type {
        flex: 1 1 8rem;
    }

    /* ─── W3. Нижний ряд действий конструктора ───────────────────────
       «Сохранить», «Отмена» и «Добавить вопрос» — около 396px в строку.
       `flex-basis: 100%` перекрывает и инлайновый `width: auto` у кнопки
       сохранения: у флекс-элемента основа сильнее ширины. */
    .poll-form-actions {
        flex-wrap: wrap;
    }

    .poll-form-actions > * {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        flex: 1 1 100%;
        min-height: 44px;
        margin: 0;
    }

    /* Кнопка добавления варианта внутри вопроса. */
    .q-add-opt {
        justify-content: center;
        width: 100%;
        min-height: 44px;
    }

    /* ─── W4. Шкала оценки ───────────────────────────────────────────
       Границы шкалы задаёт автор опроса и доходят до сотни значений. Ряд
       с переносом превращался в двадцать рядов по пять кнопок, и шкала
       переставала читаться как шкала: цветовые тиры (красный → жёлтый →
       зелёный) идут по значению и имеют смысл только в одну линию.

       Поэтому ряд не переносится, а прокручивается по горизонтали.
       Кнопки при этом сохраняют пальцевые 44px: уменьшать их — значит
       менять одну непригодность на другую. Прокрутка своя, страницу за
       собой не утаскивает. */
    .poll-scale-options {
        flex-wrap: nowrap;
        overflow-x: auto;
        overscroll-behavior-x: contain;
        touch-action: pan-x pan-y;
        scroll-snap-type: x proximity;
        padding-bottom: .35rem;
    }

    .poll-scale-opt {
        scroll-snap-align: center;
    }

    .poll-scale-row {
        display: block;
    }

    /* Ориентир для прокрученного ряда: где шкала начинается и кончается. */
    .poll-scale-hint {
        display: flex;
        justify-content: space-between;
        margin-top: .1rem;
        font-size: .72rem;
        color: rgba(255, 255, 255, .45);
    }

    [data-theme="light"] .poll-scale-hint {
        color: rgba(0, 0, 0, .5);
    }

    /* ─── W5. Ряды результатов и карточек ────────────────────────────
       Шапки собраны из заголовка и метки с `flex-shrink: 0`: метка
       забирает свою ширину, а заголовок сжимается до нескольких слов. */
    .poll-result-q-head,
    .poll-card-head {
        flex-wrap: wrap;
    }

    .poll-result-q-text,
    .poll-card-title {
        flex: 1 1 100%;
    }

    /* Ряд кнопок шапки результатов переносится общим правилом шапки
       (K2 выше), здесь кнопкам задаётся пальцевый размер. Классы и
       количество кнопок в ряду не трогаются — их фиксирует
       tests/test_polls_layout.py. */
    .app-topbar .btn-poll,
    .poll-card-actions .btn-poll {
        min-height: 44px;
    }

    /* ─── W6. Подписи внутри стек-бара ───────────────────────────────
       Сегмент подписан «12 (34%)» при `white-space: nowrap`, а прячется
       подпись только при доле меньше 8%. На 288px ширины бара это 23px
       под подпись в 50px: обрезаны почти все сегменты. Подписи убираются
       целиком — те же числа стоят в легенде под баром, она в разметке
       уже есть и переносится. */
    .results-stack-bar__segment .seg-label {
        display: none;
    }

    .results-stack-bar {
        height: 28px;
    }

    .results-question-card {
        padding: 14px 14px;
    }

    /* Заголовок вопроса не должен ужиматься меткой типа до пары слов. */
    .results-question-card__head h3 {
        flex: 1 1 100%;
        min-width: 0;
    }

    .results-legend {
        gap: 8px 14px;
    }

    /* ─── W7. Кольцевая диаграмма ────────────────────────────────────
       Легенда переезжает вниз (позиция задаётся в конфигурации графика,
       см. polls/results.html), и обёртке нужна высота под неё: 280px на
       кольцо и легенду в столбик не хватает. */
    .poll-result-chart-wrap {
        height: 360px;
    }

    /* ─── W8. Модалки опросов в событиях и идеях ─────────────────────
       Раскладка тела уже колоночная, ряды типа ответа и флажков имеют
       перенос. Кнопку закрытия во всю ширину даёт общий механизм через
       футер bootstrap-модалки (он переведён в столбец пакетом общих
       компонентов) — дублировать не нужно. Остаётся размер кнопки
       удаления варианта: 30px в ряду с полем ввода. */
    .event-poll-option-row .btn-remove {
        flex-shrink: 0;
        min-width: 44px;
        min-height: 44px;
    }

    .event-poll-add-option {
        min-height: 44px;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   НИЖЕ 768px — ТАБЛИЦЫ КАРТОЧКАМИ
   ═══════════════════════════════════════════════════════════════════

   В проекте двадцать одна таблица: от четырёх колонок до восемнадцати у
   супер-администратора в справочнике сотрудников. На экране 320–430px
   плотная таблица нечитаема при любой прокрутке: горизонтальная прокрутка
   есть не у всех, а там, где есть, приходится возить экран туда-сюда,
   теряя из виду заголовки. Три таблицы новостей вообще лежат в обёртке со
   скрытым переполнением — срезанные колонки «Статус» и «Действия» не
   достать ничем, то есть модерация новостей с телефона была невозможна.

   Решение общее на все таблицы: строка становится карточкой, значение
   подписывается названием своей колонки. Подписи проставляет
   app/static/js/rc-table-cards.js — он читает шапку таблицы и пишет
   ячейкам `data-col`; разметка, состав колонок и серверный рендер не
   меняются. Здесь только раскладка.

   Порог тот же, что у остальных пакетов трека. Выше него таблицы остаются
   таблицами, и ни одно правило этой секции не действует.
   ═══════════════════════════════════════════════════════════════════ */
@media (max-width: 767.98px) {

    /* ─── B1. Обёртки перестают резать и растягивать ─────────────────
       `.news-manage-wrap { overflow: hidden }` (custom.css) срезал
       колонки без всякой возможности их достать. Карточкам прокрутка не
       нужна вовсе — снимается и скрытие, и горизонтальная прокрутка у
       остальных обёрток, иначе внутри карточек остаётся мёртвая полоса.

       `.table-responsive` объявлен в самом Bootstrap (`overflow-x: auto`, без
       медиазапроса — то есть на всех ширинах), и библиотека теперь лежит в
       репозитории. Это правило его не заменяет, а намеренно отключает
       прокрутку ниже 768px, где таблица уже показана карточками. Выше порога
       правило не действует, и широкие таблицы прокручиваются как прежде. */
    .news-manage-wrap,
    .al-table-wrap,
    .table-responsive,
    .rc-tbl__scroll {
        overflow-x: visible;
        overflow-y: visible;
    }

    .news-manage-wrap {
        margin: 0;
        border: none;
        background: none;
    }

    /* ─── B2. Строка → карточка ──────────────────────────────────────
       Табличные боксы (`display: table*`) не умеют ни переносить ячейки,
       ни ограничивать их ширину, поэтому вся конструкция переводится в
       блочную. `width: auto` обязателен: у таблиц проекта ширина задана и
       правилом, и атрибутами `<th style="width:…">`. */
    table.rc-cards,
    table.rc-cards > tbody,
    table.rc-cards > tbody > tr,
    table.rc-cards > tbody > tr > td {
        display: block;
        width: auto;
        max-width: none;
    }

    /* Шапка больше не нужна: её текст переехал в подписи ячеек. */
    table.rc-cards > thead {
        display: none;
    }

    table.rc-cards {
        border-collapse: separate;
        border-spacing: 0;
    }

    table.rc-cards > tbody > tr {
        margin-bottom: .6rem;
        padding: .65rem .75rem;
        border: 1px solid rgba(217, 225, 255, .12);
        border-radius: 12px;
        background: rgba(217, 225, 255, .035);
    }

    [data-theme="light"] table.rc-cards > tbody > tr {
        border-color: rgba(0, 0, 0, .1);
        background: #fff;
    }

    /* Строка-заглушка «Нет данных» карточкой не становится. */
    table.rc-cards > tbody > tr[data-cards-skip] {
        padding: 1rem .75rem;
        text-align: center;
    }

    /* Подпись слева, значение справа. Обе колонки с `minmax(0, …)`:
       иначе длинное неразрывное значение (адрес, UPN, тема обращения)
       распирает сетку и возвращает горизонтальную прокрутку. */
    table.rc-cards > tbody > tr > td {
        display: grid;
        grid-template-columns: minmax(0, 5.5rem) minmax(0, 1fr);
        gap: .35rem .6rem;
        align-items: baseline;
        padding: .3rem 0;
        border: 0;
        box-shadow: none;
        overflow-wrap: anywhere;
        white-space: normal;
        text-align: left;
    }

    table.rc-cards > tbody > tr > td[data-cards-col]::before {
        content: attr(data-cards-col);
        font-size: .68rem;
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: .04em;
        color: rgba(217, 225, 255, .45);
        overflow-wrap: anywhere;
    }

    [data-theme="light"] table.rc-cards > tbody > tr > td[data-cards-col]::before {
        color: rgba(0, 0, 0, .45);
    }

    /* Ячейка без значения не оставляет висящую подпись. */
    table.rc-cards > tbody > tr > td[data-cards-empty] {
        display: none;
    }

    /* Ячейка без подписи (пустой `<th>`: миниатюра, флажок) занимает
       строку целиком. */
    table.rc-cards > tbody > tr > td:not([data-cards-col]) {
        display: block;
    }

    /* ─── B3. Колонка действий ───────────────────────────────────────
       Внизу карточки — ряд кнопок во всю ширину, подпись ему не нужна.
       Размер целей задаётся здесь же: кнопки действий в таблицах проекта
       от 24 до 31px по высоте. */
    table.rc-cards > tbody > tr > td.rc-cards__actions {
        display: flex;
        flex-wrap: wrap;
        gap: .5rem;
        margin-top: .35rem;
        padding-top: .55rem;
        border-top: 1px solid rgba(217, 225, 255, .1);
    }

    [data-theme="light"] table.rc-cards > tbody > tr > td.rc-cards__actions {
        border-top-color: rgba(0, 0, 0, .08);
    }

    table.rc-cards > tbody > tr > td.rc-cards__actions::before {
        display: none;
    }

    .rc-cards__actions button,
    .rc-cards__actions .btn,
    .rc-cards__actions a {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-height: 44px;
    }

    /* Формы внутри ряда действий (отклонение новости, удаление отдела)
       занимают строку целиком: рядом с кнопками они не встают. */
    .rc-cards__actions form {
        flex: 1 1 100%;
        margin: 0;
    }

    /* ─── B4. Жёсткие ширины кнопок модерации ────────────────────────
       `min-width: 148px` у кнопки и 220px у формы отклонения (custom.css)
       — ровно то, из-за чего блок решения не помещался. В карточке ряд
       кнопок и так во всю ширину. */
    .news-mod-btn,
    .news-mod-reject-form {
        min-width: 0;
    }

    .news-mod-btn {
        flex: 1 1 8rem;
    }

    /* ─── B5. Справочник сотрудников ─────────────────────────────────
       Цепочка `.emp-page-content → .table-wrap → .table-scroll` держит
       высоту остатком колонки и отдаёт прокрутку области, а не странице
       (tests/test_portal_layout_shrink.py, tests/test_layout_viewport_height.py).
       Устройство цепочки не трогается — снимается только горизонтальная
       прокрутка и минимальная высота: карточки тянутся по вертикали, и
       полоса высотой 180px им не нужна. */
    .table-scroll {
        overflow-x: visible;
        min-height: 0;
    }

    /* Заголовки таблицы сотрудников зафиксированы `nowrap` в шаблоне —
       в карточке они не участвуют, но подпись из них берётся, и лишний
       перенос строк там не нужен. */
    table.rc-cards > tbody > tr > td .cell-empty {
        opacity: .5;
    }

    /* ─── B6. Журнал действий ────────────────────────────────────────
       Дата `01.01.2026 12:00:00` и адрес заданы `nowrap`: ниже 700px
       таблица переставала сжиматься, переполнение уходило на область
       содержимого, и вбок ехал весь блок вместе с графиками и липкими
       фильтрами. В карточке переносится всё. */
    .al-table td,
    .al-table th {
        white-space: normal;
    }

    /* Липкая шапка таблицы в карточном виде не нужна: шапки нет. */
    .al-table thead th {
        position: static;
    }

    /* ─── B7. Клиентские таблицы журнала ─────────────────────────────
       Собственная вертикальная прокрутка со своей высотой внутри
       карточек оставляла бы окно в окне. */
    .rc-tbl__scroll {
        max-height: none;
    }

    .rc-tbl thead th {
        position: static;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   НИЖЕ 768px — ПАНЕЛИ НАД ТАБЛИЦАМИ
   ═══════════════════════════════════════════════════════════════════

   Строки фильтров, поиска и пагинации собраны в неразрывные ряды и
   обрезались вместе с таблицами. Кегль полей ввода здесь не задаётся:
   его уже подняли до 16px общим правилом слоя (иначе Safari на iOS
   увеличивает страницу при фокусе), и повтор только развёл бы значения.
   ═══════════════════════════════════════════════════════════════════ */
@media (max-width: 767.98px) {

    /* Строка фильтров журнала действий: селекты, две даты и две кнопки.
       Ряд собран колонками Bootstrap, каждой из них даётся полная ширина. */
    .al-filters__row > * {
        flex: 1 1 100%;
        max-width: 100%;
    }

    .al-filters {
        position: static;
    }

    .al-btn {
        width: 100%;
        justify-content: center;
    }

    /* Селектор периода в отчёте: шесть кнопок-таблеток и пара дат. */
    .rep-period-row {
        gap: .4rem;
    }

    .rep-period-row .btn {
        flex: 1 1 5.5rem;
        min-height: 44px;
    }

    /* Ряд поиска и настройки колонок в справочнике сотрудников. */
    .emp-topbar-controls {
        flex: 1 1 100%;
    }

    .emp-topbar-controls > * {
        flex: 1 1 auto;
    }

    /* Пагинация под таблицей: кнопки уже 44px (T5 выше), ряду нужен
       перенос и центрирование. */
    .emp-pager {
        flex-wrap: wrap;
        justify-content: center;
        row-gap: .35rem;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   НИЖЕ 768px — ГРАФИКИ
   ═══════════════════════════════════════════════════════════════════

   Три графика отчёта заданы адаптивными, но без отключения расчёта
   высоты по соотношению сторон: высота считалась как ширина, делённая на
   отношение из атрибутов холста, поэтому на узком экране график сжимался
   в полоску. Отключение вынесено в шаблон (там же, где конфигурация), а
   определённую высоту графику обязана дать обёртка — иначе Chart.js без
   соотношения сторон не знает, куда рисовать.
   ═══════════════════════════════════════════════════════════════════ */
@media (max-width: 767.98px) {

    .report-chart-box {
        height: 240px;
    }

    /* Полосы по отделам растут числом: высоту задаёт шаблон исходя из их
       количества, здесь только нижняя граница. */
    .report-chart-box--depts {
        height: auto;
        min-height: 200px;
    }

    .al-chart-wrap {
        height: 220px;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   НИЖЕ 768px — КАРТОЧНЫЕ СЕТКИ РАЗДЕЛОВ
   ═══════════════════════════════════════════════════════════════════

   Пять сеток объявлены с минимальной шириной колонки 280–320px и не
   имеют мобильного сброса. `auto-fill` с `minmax(320px, 1fr)` при 288px
   доступной ширины даёт одну колонку шириной 320px — то есть колонка
   шире контейнера, и содержимое выпирает. Плюс у списка новостей поверх
   отступов страницы стоят собственные 1.5rem с каждой стороны, а сетка
   инструментов портала сбрасывается, но только до двух колонок — на
   375px это около 40px на колонку с учётом отступов.

   Сетки, у которых сброс в одну колонку уже есть, здесь не повторяются:
   `.portal-grid` (сброс на 1100px), `.profile-grid` (1100px), `.cal-dash`
   и списки опросов проверены и не трогаются. Мини-карточки блока новостей
   (`.nb-mini-top`) из списка ушли вместе с самой раскладкой: новости на
   главной идут лентой `.portal-feed`, а она сеткой не является — карточки
   в ней одной ширины на любом экране, и сбрасывать нечего.
   Сетки `.dash-cards` здесь тоже нет:
   ни один шаблон и ни один скрипт её не использует — правило в custom.css
   мёртвое, а чинить мёртвый код незачем.
   ═══════════════════════════════════════════════════════════════════ */
@media (max-width: 767.98px) {

    .news-list,
    .ideas-grid,
    .adaptation-plans,
    .o2o-cards {
        grid-template-columns: 1fr;
    }

    /* Список новостей вложен в область содержимого с её собственными
       отступами — свои 1.5rem по краям он добавлял поверх. */
    .news-list {
        padding: 0 0 1rem;
    }
    /* Поле поиска новостей — по образцу R2 для `.chart-search`: строка
       целиком, высота 44px, крестик с расширенной до 44px областью нажатия.
       Кегль задаёт общее правило «16px всем полям» ниже 992px. */
    .news-search {
        padding: 0 0 1rem;
    }
    /* Панель категорий — без боковых полей, как поиск; метки выше, чтобы
       в них попадал палец. */
    .news-cat-bar {
        padding: 0 0 1rem;
    }
    .news-cat-chip {
        min-height: 36px;
    }
    .news-search .input-wrap {
        flex: 1 1 100%;
    }
    .news-search input {
        min-height: 44px;
        padding: .5rem 2.6rem .5rem 2.4rem;
    }
    .news-search .search-icon {
        left: .85rem;
        font-size: .9rem;
    }
    .news-search .clear-btn {
        right: .8rem;
        font-size: 1rem;
    }
    .news-search .clear-btn::after {
        content: '';
        position: absolute;
        top: 50%;
        left: 50%;
        width: 44px;
        height: 44px;
        transform: translate(-50%, -50%);
        background: transparent;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   НИЖЕ 768px — ПРОФИЛЬ, НОВОСТИ, ИДЕИ
   ═══════════════════════════════════════════════════════════════════ */
@media (max-width: 767.98px) {

    /* ─── P1. Шапка профиля ──────────────────────────────────────────
       Аватар 96px, поля 1.5rem и промежуток 1.25rem оставляли под ФИО
       около 163px, а кнопка редактирования стоит абсолютом в правом
       верхнем углу и ложилась прямо на заголовок. Содержимое встаёт в
       столбец, кнопка возвращается в поток. */
    .profile-hero {
        flex-direction: column;
        gap: .85rem;
        padding: 1.1rem;
    }

    .profile-hero-photo {
        width: 68px;
        height: 68px;
    }

    .profile-hero-photo.emp-avatar-fallback {
        font-size: 24px;
    }

    .profile-hero-name {
        font-size: 1.2rem;
    }

    .profile-edit-btn {
        position: static;
        align-self: flex-start;
        min-height: 44px;
        display: inline-flex;
        align-items: center;
    }

    /* ─── P2. Обложка новости ────────────────────────────────────────
       320px фиксированной высоты — почти весь первый экран под картинку.
       Та же задача на главной решена соотношением сторон, здесь тот же
       приём: высота считается от ширины и остаётся пропорциональной. */
    .news-detail-cover {
        height: auto;
        aspect-ratio: 16 / 9;
        margin: -1.1rem -1.1rem 1.1rem;
    }

    /* Шапка карточки в списке новостей: метка категории и дата разведены
       по краям без переноса. */
    .news-card-header {
        flex-wrap: wrap;
    }

    /* ─── P3. Формы на детальной странице идеи ───────────────────────
       Смена статуса и заявка на роль собраны в неразрывный ряд: список,
       поле сообщения шириной 220px и кнопка — около 470px. */
    .idea-inline-form {
        display: flex;
        flex-wrap: wrap;
        width: 100%;
    }

    .idea-inline-form > * {
        flex: 1 1 100%;
    }

    .idea-inline-form .form-select,
    .idea-apply-message {
        width: 100%;
        min-width: 0;
    }

    .idea-inline-form .btn-poll {
        justify-content: center;
        min-height: 44px;
    }

    /* ─── P4. Метка нового обращения ─────────────────────────────────
       Стоит абсолютом в правом верхнем углу карточки, а у темы обращения
       запаса справа нет — метка ложилась прямо на текст. */
    .ticket-card-subject {
        padding-right: 5rem;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   НИЖЕ 768px — АДАПТАЦИЯ, СТАТУСЫ ИДЕЙ, УВЕДОМЛЕНИЯ
   ═══════════════════════════════════════════════════════════════════ */
@media (max-width: 767.98px) {

    /* ─── A1. Этапы и задачи адаптации ───────────────────────────────
       Заголовок этапа разводит название и кнопки по краям без переноса,
       ряд задачи собран из отметки, названия, срока и ответственного. */
    .adaptation-stage-header {
        flex-wrap: wrap;
        gap: .5rem;
    }

    .adaptation-stage-header h3 {
        flex: 1 1 100%;
    }

    .adaptation-task-row {
        flex-wrap: wrap;
        row-gap: .35rem;
    }

    .adaptation-task-info {
        flex: 1 1 100%;
    }

    /* Остальные ряды раздела: шапки блоков, строка добавления этапа, шапка
       конструктора шаблонов, подписи комментариев и заметок. Все собраны по
       одному образцу — заголовок и действия по краям без переноса, поэтому и
       лечатся одним списком. Шапки виджета адаптации в списке больше нет:
       в разметке виджета её не было ни на одной странице. */
    .adaptation-plan-head,
    .adaptation-block-head,
    .adaptation-builder-header,
    .adaptation-stage-head,
    .adaptation-stage-add,
    .adaptation-comment-head,
    .o2o-note-head {
        flex-wrap: wrap;
        gap: .5rem;
    }

    /* Ряды действий в теле страницы: у шапок раздела перенос уже есть
       (общее правило `.app-topbar` выше), а эти лежат в содержимом. */
    .page-form-actions {
        flex-wrap: wrap;
        gap: .5rem;
    }

    .page-form-actions > .btn,
    .page-form-actions > button {
        flex: 1 1 100%;
        min-height: 44px;
    }

    /* Пара полей даты в форме встречи: у поля компонента выбора даты
       минимальная ширина 180px, вдвоём с промежутком это 368px. */
    .o2o-date-row {
        flex-wrap: wrap;
    }

    .o2o-date-row > * {
        flex: 1 1 100%;
    }

    /* ─── A2. Строка статуса идей ────────────────────────────────────
       Ручка, название, счётчик, до трёх меток и три кнопки в один ряд.
       Ручка остаётся в первой строке — за неё держится перетаскивание
       (SortableJS), и её размер задан слоем тач-таргетов выше. */
    .is-row {
        flex-wrap: wrap;
        row-gap: .4rem;
    }

    .is-name {
        flex: 1 1 8rem;
        min-width: 0;
    }

    .is-actions {
        flex: 1 1 100%;
        display: flex;
        flex-wrap: wrap;
        gap: .5rem;
    }

    .is-actions .btn {
        flex: 1 1 6rem;
        min-height: 44px;
    }

    /* ─── A3. Список уведомлений ─────────────────────────────────────
       Ряд элемента (значок, текст, точка непрочитанного) и ряд меток
       уже переносятся или тянутся; чинить нужно только пагинацию: она
       собрана из двух кнопок и подписи «Страница N из M» в один ряд.
       Шапку страницы переносит общее правило `.app-topbar` выше. */
    .notif-pagination {
        flex-wrap: wrap;
        gap: .5rem;
    }

    .notif-pagination .btn-dashboard-secondary {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
    }

    .notif-item {
        padding: .85rem .8rem;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   РЕЖИМ ПРИЛОЖЕНИЯ — БЕЗОПАСНЫЕ ЗОНЫ
   ═══════════════════════════════════════════════════════════════════

   Портал ставится на домашний экран и запускается без адресной строки
   (`display: standalone` в манифесте, маршрут `main.webmanifest`). Полосы
   браузера сверху и снизу при этом исчезают, а системные элементы —
   вырез с часами сверху и домашняя черта снизу — остаются, и содержимое
   уходит прямо под них. В обычном браузере то же самое включает
   `viewport-fit=cover` в мета-вьюпорте, но там край экрана прикрывали
   панели самого браузера, и цена ошибки была меньше.

   Большинство отступов расставлено по месту — там, где элемент упирается в
   край экрана: шторка навигации и её подвал, мобильная шапка, область
   содержимого, панель уведомлений, всплывающие окна, панель мастера
   опросов, подсказка тура. Здесь собрано то, что не принадлежит ни одному
   разделу: стандартные окна Bootstrap и страницы входа, живущие вне
   каркаса портала.

   Переменные `--safe-*` объявлены в начале файла с фолбэком 0px, поэтому на
   устройствах без выреза и в окне обычного браузера все правила ниже дают
   ровно прежние значения.
   ═══════════════════════════════════════════════════════════════════ */
@media (max-width: 991.98px) {

    /* Окно Bootstrap занимает почти весь экран, а его футер ниже 992px
       выложен столбцом кнопок во всю ширину (см. раздел про модалки), и
       нижняя кнопка — «Отмена» либо «Закрыть» — приходилась ровно на
       домашнюю черту. Переопределяются только вертикальные поля:
       горизонтальные у бутстрапа `auto`, и трогать их нельзя — окно
       перестанет стоять по центру. */
    .modal-dialog {
        margin-top: calc(.5rem + var(--safe-top));
        margin-bottom: calc(.5rem + var(--safe-bottom));
    }

    /* Вход и регистрация — единственные страницы вне каркаса портала: ни
       мобильной шапки, ни `.page-content` у них нет, а карточка на
       невысоком экране и в ландшафте прижимается к краям вплотную.
       Установленное приложение открывается именно здесь, если сессия
       кончилась, поэтому отступы нужны и им. */
    body.auth-page {
        padding: calc(1rem + var(--safe-top)) calc(1rem + var(--safe-right))
                 calc(1rem + var(--safe-bottom)) calc(1rem + var(--safe-left));
    }
}


/* ═══════════════════════════════════════════════════════════════════
   ДОСТУПНОСТЬ
   Запрос сдвоенный намеренно: вне мобильного диапазона у `.app-sidebar`
   есть своя транзишн (цвет скроллбара по наведению), и гасить её незачем.
   ═══════════════════════════════════════════════════════════════════ */
@media (max-width: 991.98px) and (prefers-reduced-motion: reduce) {
    .app-sidebar,
    .mnav-overlay {
        transition: none;
    }
}


/* ═══════════════════════════════════════════════════════════════════
   НИЖЕ 992px — РАЗДЕЛЫ ТОЛЬКО ДЛЯ КОМПЬЮТЕРА
   ═══════════════════════════════════════════════════════════════════

   Мобильный портал рассчитан на рядового сотрудника: администрирование с
   телефона не нужно и мешает — таблица сотрудников, отчёты по SLA, журнал
   действий, настройки синхронизации и формы управления новостями на узком
   экране бесполезны. Ничего не удаляется: выше 992px всё работает как
   работало, здесь разделы просто не показываются.

   Порог тот же, на котором сайдбар уезжает в выдвижную панель, — 992px.
   Один порог на весь мобильный слой: разойдись они, между 768 и 992
   получилась бы полоса, где меню уже шторка, а разделы ещё «настольные».

   Приёмов ровно два, оба через `display: none`, и это не вкусовой выбор:
   выключенный так узел не берёт фокус с клавиатуры (Tab его пропускает) и
   не читается голосовым доступом. `visibility: hidden` даёт то же самое, но
   оставляет занятое место, а `opacity`/`aria-hidden` — ни того, ни другого.

   1. `.desktop-only-link` — пункт меню или кнопка, ведущие в скрытый
      раздел. Разметка остаётся на месте: порядок сайдбара персональный и
      собирается по `[data-nav-key]` из DOM (см. комментарий в
      app_layout.html), а пропавший из разметки пункт уехал бы в конец
      списка на компьютере.

   2. `.desktop-only` — заглушка прямого захода. Показывается вместо
      содержимого страницы, само содержимое при этом выключается. Права
      доступа не затронуты: маршрут отрабатывает как обычно, и роль без
      доступа по-прежнему получает 403 — до каркаса дело не доходит.
   ═══════════════════════════════════════════════════════════════════ */
@media (max-width: 991.98px) {

    /* ─── U1. Пункты меню и кнопки скрытых разделов ──────────────────── */
    .desktop-only-link {
        display: none;
    }

    /* ─── U2. Содержимое страницы уступает место заглушке ─────────────
       Гасится ВСЁ, что каркас положил в колонку, кроме самой заглушки и
       мобильной шапки с бургером: из заглушки нужно уметь выйти в меню.
       Перечислять исключения, а не сами блоки страницы, приходится потому,
       что разметка у разделов разная — общего контейнера содержимого в
       проекте нет (у части страниц это `.page-content`, у части —
       собственная полноэкранная область). */
    body.desktop-only-page .app-main > :not(.mobile-bar):not(.desktop-only) {
        display: none;
    }

    /* ─── U3. Сама заглушка ───────────────────────────────────────────
       Оформление повторяет карточку страницы ошибки (`.error-page__card` в
       custom.css) — те же токены палитры, обе темы, тот же шрифт портала:
       заглушка должна читаться как часть портала, а не как сбой. Правила
       свои, а не общие с ошибкой: у страницы ошибки другой состав (код
       ответа, кнопка «Назад») и своя жизнь на десктопе. */
    .desktop-only {
        display: flex;
        flex: 1;
        min-height: 0;
        overflow-y: auto;
        align-items: center;
        justify-content: center;
        padding: 2rem 1rem;
        padding-bottom: calc(2rem + var(--safe-bottom));
    }

    .desktop-only__card {
        width: 100%;
        max-width: 460px;
        text-align: center;
        padding: 1.75rem 1.25rem;
        border-radius: 16px;
        background: var(--tk-card-bg-2);
        border: 1px solid var(--tk-card-border);
    }

    .desktop-only__icon {
        display: block;
        font-size: 1.8rem;
        line-height: 1;
        margin-bottom: .9rem;
        color: var(--ac);
    }

    .desktop-only__title {
        font-size: 1.15rem;
        font-weight: 700;
        margin: 0 0 .5rem;
        color: var(--tk-text);
    }

    .desktop-only__text {
        font-size: .85rem;
        line-height: 1.55;
        margin: 0 0 1.4rem;
        color: var(--tk-muted);
    }

    /* Кнопка возврата — не мельче принятых в проекте 44px: это
       единственное действие на экране. */
    .desktop-only__btn {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-height: 44px;
        padding: .5rem 1.1rem;
        border-radius: 9px;
        font-size: .85rem;
        font-weight: 600;
        text-decoration: none;
        background: var(--ac-dim);
        border: 1px solid var(--ac-border);
        color: var(--ac);
    }
}

/* Заглушка отдаётся сервером на любой ширине — знать её он не может.
   Выше 992px узла быть не должно: раздел работает как работал.

   Порог зеркальный порогу блока выше и не пересекается с ним: 991.98px
   против 992px. Приём тот же, что у `.rc-mclose`: мобильная разметка
   выключается собственным правилом, а не остаётся видимой. */
@media (min-width: 992px) {
    .desktop-only {
        display: none;
    }
}
