:root {
    /* Тёмная тема — значения по умолчанию. Светлая переопределяет те же токены
       в блоке [data-theme="light"] ниже, поэтому цвета интерфейса переключаются
       целиком: правил с «зашитым» цветом, который не зависит от темы, остаться
       не должно — иначе он не переключится. */
    --bg: #05060a;
    /* Общий фон приложения: цвет (--bg) плюс узор (--bg-art). Тёмная тема — космос:
       мелкие повторяемые плитки звёзд разного размера (одинаковый шаг читался бы
       сеткой) поверх двух туманностей во весь экран. Светлая тема переопределяет
       те же три токена ниже — там небо.
       Слои идут сверху вниз: звёзды, потом туманности, под ними цвет.
       Звёзды и облака держим бледными (максимум ~0.6 альфы): это фон для переписки,
       он не должен перетягивать внимание на себя. */
    --bg-art:
        radial-gradient(1.6px 1.6px at 24px 36px, rgba(255, 255, 255, 0.5) 50%, transparent 51%),
        radial-gradient(1.1px 1.1px at 132px 92px, rgba(255, 255, 255, 0.32) 50%, transparent 51%),
        radial-gradient(2.2px 2.2px at 58px 158px, rgba(255, 255, 255, 0.6) 50%, transparent 51%),
        radial-gradient(1.1px 1.1px at 190px 40px, rgba(196, 214, 255, 0.38) 50%, transparent 51%),
        radial-gradient(1.4px 1.4px at 96px 210px, rgba(255, 255, 255, 0.26) 50%, transparent 51%),
        radial-gradient(900px 620px at 12% 4%, rgba(94, 92, 230, 0.26), transparent 68%),
        radial-gradient(760px 520px at 88% 94%, rgba(10, 132, 255, 0.2), transparent 72%);
    --bg-art-size:
        240px 240px, 200px 200px, 340px 340px, 420px 420px, 300px 300px,
        100% 100%, 100% 100%;
    --bg-art-repeat: repeat, repeat, repeat, repeat, repeat, no-repeat, no-repeat;
    --fill: #1c1c1e;
    --fill-2: #2c2c2e;
    --fill-3: #3a3a3c;
    --well: #2c2c2e;
    /* Поле в фокусе: чуть светлее обычной заливки. */
    --well-focus: #2e2e31;
    --line: rgba(255, 255, 255, 0.08);
    --line-soft: rgba(255, 255, 255, 0.06);
    --text: #f5f5f7;
    --muted: rgba(235, 235, 245, 0.55);
    --muted-soft: rgba(235, 235, 245, 0.42);
    --placeholder: rgba(235, 235, 245, 0.28);
    --accent: #0a84ff;
    --accent-press: #409cff;
    --online: #30d158;
    --danger: #ff453a;
    --font: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    --sidebar: 272px;
    /* Стеклянные поверхности: полупрозрачная заливка + размытие того,
       что лежит под панелью. */
    --glass: saturate(180%) blur(20px);
    --material: rgba(28, 28, 30, 0.72);
    --material-bright: rgba(44, 44, 46, 0.72);
    /* Стеклянные панели (шапка чата, композер, боковая колонка) и пузыри. */
    --panel: rgba(28, 28, 30, 0.55);
    /* Полупрозрачные заливки и подсветки: в тёмной теме они белые, в светлой —
       чёрные, иначе на светлом фоне подсветка не видна вовсе. */
    --wash-1: rgba(255, 255, 255, 0.03);
    --wash-2: rgba(255, 255, 255, 0.06);
    --wash-3: rgba(255, 255, 255, 0.09);
    --wash-4: rgba(255, 255, 255, 0.16);
    --wash-5: rgba(255, 255, 255, 0.22);
    /* Дорожка сегментных переключателей: вкладки входа, «Контакты/Группы», тема. */
    --track: rgba(118, 118, 128, 0.16);
    /* Активный сегмент переключателя: светлая плашка с тёмным текстом. */
    --seg-active: rgba(120, 120, 128, 0.32);
    --seg-text: #fff;
    /* Текст чужого пузыря, свой пузырь и его заливка. */
    --bubble-text: #f2f2f7;
    --bubble-mine: rgba(10, 132, 255, 0.25);
    /* «Дорожки» голосового сообщения поверх чужого пузыря. */
    --wave: rgba(255, 255, 255, 0.32);
    /* Затемнение под панелью звонка. */
    --veil: rgba(5, 6, 10, 0.82);
    --shadow: rgba(0, 0, 0, 0.35);
}

/* Светлая тема: те же токены в светлых значениях. Ставится атрибутом
   data-theme на <html> — его выставляет js/theme.js ещё до первой отрисовки. */
html[data-theme="light"] {
    color-scheme: light;
    --bg: #f2f2f7;
    /* Небо: свет сверху вниз плюс мягкие облака поверх него. */
    --bg-art:
        radial-gradient(260px 130px at 16% 18%, rgba(255, 255, 255, 0.5), transparent 70%),
        radial-gradient(320px 150px at 58% 8%, rgba(255, 255, 255, 0.42), transparent 72%),
        radial-gradient(280px 140px at 88% 30%, rgba(255, 255, 255, 0.36), transparent 70%),
        radial-gradient(340px 160px at 32% 80%, rgba(255, 255, 255, 0.26), transparent 72%),
        linear-gradient(180deg, #8cc6ff 0%, #c3e0ff 46%, #eef6ff 100%);
    --bg-art-size: 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%;
    --bg-art-repeat: no-repeat, no-repeat, no-repeat, no-repeat, no-repeat;
    --fill: #ffffff;
    --fill-2: #e9e9ee;
    --fill-3: #d5d5db;
    --well: #e9e9ee;
    --well-focus: #dedee4;
    --line: rgba(60, 60, 67, 0.18);
    --line-soft: rgba(60, 60, 67, 0.12);
    --text: #1c1c1e;
    --muted: rgba(60, 60, 67, 0.6);
    --muted-soft: rgba(60, 60, 67, 0.45);
    --placeholder: rgba(60, 60, 67, 0.35);
    --material: rgba(255, 255, 255, 0.74);
    --material-bright: rgba(250, 250, 252, 0.8);
    --panel: rgba(255, 255, 255, 0.66);
    --wash-1: rgba(0, 0, 0, 0.03);
    --wash-2: rgba(0, 0, 0, 0.05);
    --wash-3: rgba(0, 0, 0, 0.07);
    --wash-4: rgba(0, 0, 0, 0.12);
    --wash-5: rgba(0, 0, 0, 0.18);
    --track: rgba(118, 118, 128, 0.12);
    --seg-active: #ffffff;
    --seg-text: #1c1c1e;
    --bubble-text: #1c1c1e;
    /* Свой пузырь сплошной: полупрозрачный акцент на светлом фоне выцветал. */
    --bubble-mine: #0a84ff;
    --wave: rgba(0, 0, 0, 0.28);
    --veil: rgba(0, 0, 0, 0.55);
    --shadow: rgba(0, 0, 0, 0.16);
}

* {
    box-sizing: border-box;
    /* Мобильные Chrome и Safari рисуют по тапу полупрозрачный синий прямоугольник
       поверх элемента. Свои отклики на нажатие в интерфейсе есть (:active),
       поэтому встроенную подсветку гасим — иначе она накрывает кнопки дока,
       композер и листы. Контур фокуса это не трогает: с клавиатуры он остаётся. */
    -webkit-tap-highlight-color: transparent;
    /* Полосы прокрутки — свои и в тон темы: системная тянет за собой дорожку,
       которая на стеклянных панелях читается как чужая полоса. Дорожку делаем
       прозрачной (сквозь неё виден фон панели), а бегунок — та же полупрозрачная
       заливка, что у подсветок интерфейса. Правило на все контейнеры сразу,
       чтобы лента, список чатов, контакты и листы прокручивались одинаково.
       Стандартные свойства слушают Chromium и Firefox; Safari их не знает и рисует
       свои наложения — переопределить их нечем, но они и так почти не видны. */
    scrollbar-width: thin;
    scrollbar-color: var(--wash-4) transparent;
}

html {
    color-scheme: dark;
}

html, body {
    margin: 0;
    height: 100%;
    overflow: hidden;
    color: var(--text);
    font-family: var(--font);
    font-optical-sizing: auto;
    -webkit-font-smoothing: antialiased;
}

/* Общий фон один на все экраны — загрузка, вход и сам чат: цвет плюс узор из
   токенов темы. Держим его одним правилом и не повторяем `background: var(--bg)`
   в каждой панели: иначе фон разъезжается между экранами при переключении темы.
   Панели поверх полупрозрачные, так что узор виден и через них. */
html,
body,
.lobby,
.boot,
.room {
    background-color: var(--bg);
    background-image: var(--bg-art);
    background-size: var(--bg-art-size);
    background-repeat: var(--bg-art-repeat);
}

body {
    height: 100dvh;
}

[hidden] {
    display: none !important;
}

.lobby {
    position: relative;
    height: 100%;
    overflow: auto;
    display: grid;
    place-items: center;
    padding: 24px;
}

/* Стартовый экран: пока проверяем сохранённую сессию, показываем логотип,
   а не форму входа (иначе она мелькает при каждом обновлении страницы). */
.boot {
    position: fixed;
    inset: 0;
    z-index: 5;
    display: grid;
    place-content: center;
    justify-items: center;
    gap: 12px;
}

.boot-mark {
    display: grid;
    place-items: center;
    width: 46px;
    height: 46px;
    color: #fff;
    background: var(--accent);
    border-radius: 10px;
    animation: boot-pulse 1.4s ease-in-out infinite;
}

.boot-name {
    margin: 0;
    color: var(--muted);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
}

@keyframes boot-pulse {
    0%, 100% { opacity: 1; transform: scale(1); }
    50% { opacity: 0.7; transform: scale(0.96); }
}

.lobby-card {
    position: relative;
    z-index: 1;
    width: min(960px, 100%);
    animation: rise-in 0.55s cubic-bezier(0.22, 1, 0.36, 1);
}

.auth-card {
    width: min(400px, 100%);
    padding: 28px 24px 24px;
    border: 1px solid var(--line);
    border-radius: 12px;
    background: var(--material);
    backdrop-filter: var(--glass);
    -webkit-backdrop-filter: var(--glass);
}

.auth-tabs {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 3px;
    margin: 0 0 14px;
    padding: 3px;
    border-radius: 8px;
    background: var(--track);
}

.auth-tab {
    height: 34px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--muted);
    font-size: 14px;
    font-weight: 600;
    transition: background 0.18s ease, box-shadow 0.18s ease, color 0.18s ease;
}

.auth-tab.active {
    background: var(--seg-active);
    color: var(--seg-text);
}

.account-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin: 0 0 12px;
}

.account-bar span {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 15px;
    font-weight: 600;
    letter-spacing: -0.02em;
}

.lobby-brand {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    margin: 0 0 22px;
}

.lobby-split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(280px, 360px);
    gap: 28px 36px;
    align-items: stretch;
}

.lobby-join {
    display: grid;
    justify-items: stretch;
    align-content: start;
    min-width: 0;
}

.media-setup {
    position: relative;
    min-width: 0;
    min-height: 0;
}

.preview {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    height: 100%;
    width: auto;
    max-width: 100%;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    border-radius: 12px;
    background: var(--fill);
    border: 1px solid var(--fill-3);
}

.app-mark {
    width: 46px;
    height: 46px;
    margin: 0;
    display: grid;
    place-items: center;
    flex-shrink: 0;
    color: #fff;
    background: var(--accent);
    border-radius: 10px;
}

.eyebrow {
    margin: 0;
    font-size: 17px;
    font-weight: 650;
    letter-spacing: -0.03em;
    color: var(--text);
}

.error {
    margin: 10px 0 0;
    max-width: 34ch;
    color: var(--danger);
    font-size: 14px;
    line-height: 1.4;
}

.preview-thread {
    position: absolute;
    inset: 0;
    z-index: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 8px;
    padding: 22px 18px;
}

.bubble {
    max-width: 78%;
    padding: 6px 10px 5px;
    border-radius: 10px 10px 10px 3px;
    background: var(--fill-2);
    color: var(--text);
    font-size: 14px;
    font-weight: 500;
    line-height: 1.3;
    letter-spacing: -0.02em;
}

.bubble.mine {
    align-self: flex-end;
    border-radius: 10px 10px 3px 10px;
    background: var(--accent);
}

.preview-face {
    position: absolute;
    inset: 0;
    z-index: 1;
    display: grid;
    place-items: center;
    font-size: clamp(42px, 8vw, 72px);
    font-weight: 600;
    letter-spacing: -0.04em;
    line-height: 1;
}

.preview-name {
    position: absolute;
    left: 10px;
    bottom: 12px;
    z-index: 3;
    max-width: calc(50% - 12px);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    padding: 5px 10px;
    border-radius: 8px;
    background: var(--well);
    font-size: 13px;
    font-weight: 600;
    line-height: 1.2;
}

.join-form {
    display: grid;
    gap: 12px;
    width: 100%;
    text-align: left;
}

.field-group {
    display: grid;
    gap: 10px;
    width: 100%;
    overflow: visible;
    background: transparent;
    border: 0;
    border-radius: 0;
    box-shadow: none;
}

.field {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-rows: auto auto;
    align-items: center;
    align-content: center;
    column-gap: 8px;
    row-gap: 4px;
    margin: 0;
    min-height: 56px;
    padding: 12px 16px;
    background: var(--well);
    border: 0.5px solid var(--line);
    border-radius: 10px;
    transition: background 0.18s ease, border-color 0.18s ease;
}

.field:last-child {
    border-bottom: 0.5px solid var(--line-soft);
}

.field > label {
    grid-column: 1;
    grid-row: 1;
    font-size: 11px;
    font-weight: 600;
    line-height: 1.2;
    letter-spacing: 0.02em;
    color: var(--muted);
    text-transform: uppercase;
}

.field-action {
    grid-column: 2;
    grid-row: 1 / span 2;
    align-self: center;
    width: 28px;
    height: 28px;
    display: grid;
    place-items: center;
    padding: 0;
    border: 0;
    border-radius: 8px;
    background: var(--fill-3);
    color: var(--accent);
    transition: background 0.18s ease, transform 0.16s ease, color 0.18s ease;
}

.field-action:hover {
    background: var(--fill-3);
}

.field-action:active {
    transform: scale(0.9);
}

.field-action:focus-visible {
    outline: 2px solid var(--accent-press);
    outline-offset: 2px;
}

.field-action svg {
    display: block;
}

.field input {
    grid-column: 1;
    grid-row: 2;
    min-width: 0;
    height: 26px;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: transparent;
    font-size: 16px;
    font-weight: 400;
    line-height: 26px;
    letter-spacing: -0.02em;
}

.field:focus-within {
    background: var(--well-focus);
    border-color: rgba(10, 132, 255, 0.5);
    box-shadow: 0 0 0 3px rgba(10, 132, 255, 0.16);
}
input[type="text"],
input[type="password"],
textarea {
    width: 100%;
    color: var(--text);
    font: inherit;
    border: 1px solid var(--line);
}

.field input {
    border: 0;
}

input[type="text"]::placeholder,
input[type="password"]::placeholder,
textarea::placeholder {
    color: var(--placeholder);
}

input[type="text"]:focus,
input[type="password"]:focus,
textarea:focus {
    outline: none;
}

button {
    font: inherit;
    cursor: pointer;
}

.join-form > button {
    width: 100%;
    height: 48px;
    border: 0;
    border-radius: 10px;
    background: var(--accent);
    color: #fff;
    font-size: 16px;
    font-weight: 600;
    letter-spacing: -0.02em;
    transition: background 0.18s ease;
}

.join-form > button:hover {
    background: var(--accent-press);
}

.join-form > button:active {
    background: #0066d6;
}

.join-form > button:focus-visible {
    outline: 2px solid var(--accent-press);
    outline-offset: 3px;
}

.auth-link {
    display: block;
    width: 100%;
    margin-top: 8px;
    padding: 8px 0;
    border: 0;
    background: transparent;
    color: var(--accent);
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    text-align: center;
}

.auth-link:hover {
    color: var(--accent-press);
}

.uin-board {
    display: grid;
    justify-items: center;
    gap: 8px;
    margin: 0 0 4px;
    padding: 18px 16px 16px;
    border: 0.5px solid var(--line);
    border-radius: 12px;
    background: var(--well);
    text-align: center;
}

.uin-kicker {
    color: var(--muted);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.uin-value {
    margin: 0;
    color: var(--text);
    font-size: 28px;
    font-weight: 700;
    letter-spacing: 0.14em;
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
}

.uin-value-btn {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    padding: 4px 12px 4px 14px;
    border: 0;
    border-radius: 8px;
    background: var(--wash-2);
    font: inherit;
    cursor: pointer;
    transition: background 0.18s ease;
}

.uin-value-btn:hover {
    background: var(--wash-3);
}

.uin-value-btn:active {
    background: var(--wash-4);
}

.uin-value-btn:disabled {
    background: transparent;
    cursor: default;
}

.uin-copy-icon {
    display: inline-flex;
    color: var(--accent);
    transition: color 0.18s ease;
}

.uin-value-btn:disabled .uin-copy-icon {
    color: var(--muted);
}

.uin-icon-done {
    display: none;
}

.uin-value-btn.copied .uin-copy-icon {
    color: var(--online);
}

.uin-value-btn.copied .uin-icon-copy {
    display: none;
}

.uin-value-btn.copied .uin-icon-done {
    display: block;
}

.uin-reroll {
    height: 34px;
    padding: 0 12px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--accent);
    font: inherit;
    font-size: 14px;
    font-weight: 600;
    transition: opacity 0.18s ease;
}

.uin-reroll:hover {
    opacity: 0.7;
}

.uin-reroll:active {
    opacity: 0.5;
}

.uin-reroll:disabled {
    opacity: 0.45;
    cursor: default;
    color: var(--muted);
}

.uin-hint {
    margin: 0;
    color: var(--muted);
    font-size: 12px;
    line-height: 1.35;
}

.uin-search {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 32px;
    align-items: center;
    gap: 6px;
    margin: 10px 10px 4px;
    padding: 4px 4px 4px 10px;
    border: 0;
    border-radius: 10px;
    background: var(--well);
}

.uin-search input {
    height: 32px;
    padding: 0;
    border: 1px solid var(--line);
    background: transparent;
    color: var(--text);
    font: inherit;
    font-size: 13px;
}

.uin-search input:focus,
.uin-search input:focus-visible {
    outline: none;
}

.uin-search button {
    width: 32px;
    height: 32px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--muted);
}

.uin-search button:hover {
    color: var(--text);
    background: var(--wash-2);
}

.uin-search-error {
    margin: 0 12px 4px;
    font-size: 12px;
}

.ghost {
    height: 26px;
    padding: 0 10px;
    border: 0;
    border-radius: 8px;
    background: var(--fill-2);
    color: var(--accent);
    font-size: 12px;
    font-weight: 600;
    transition: background 0.18s ease, transform 0.16s ease;
}

.ghost:active {
    transform: scale(0.96);
}

.room {
    position: relative;
    display: grid;
    grid-template-columns: var(--sidebar) minmax(0, 1fr);
    height: 100%;
    overflow: hidden;
}

.sidebar,
.thread {
    position: relative;
    z-index: 1;
}

.sidebar {
    position: relative;
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
    margin: 0 10px 0 0;
    overflow: hidden;
    background: var(--panel);
    backdrop-filter: var(--glass);
    -webkit-backdrop-filter: var(--glass);
    border: 1px solid var(--line);
    border-top: 0;
    border-bottom: 0;
    border-left: 0;
    border-radius: 0;
}

.sidebar-scroll {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    overflow: hidden;
    padding-bottom: 76px;
}

.sidebar-switch {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 3px;
    margin: 10px 12px 6px;
    padding: 3px;
    border-radius: 8px;
    background: var(--track);
    flex-shrink: 0;
}

.sidebar-switch-item {
    height: 32px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--muted);
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    transition: background 0.18s ease, color 0.18s ease;
}

.sidebar-switch-item.active {
    background: var(--seg-active);
    color: var(--seg-text);
}

.switch-count {
    display: inline-grid;
    place-items: center;
    min-width: 18px;
    height: 18px;
    margin-left: 4px;
    padding: 0 5px;
    border-radius: 8px;
    background: var(--wash-3);
    font-size: 11px;
    font-weight: 700;
    line-height: 1;
    font-variant-numeric: tabular-nums;
    vertical-align: 1px;
}

.sidebar-switch-item.active .switch-count {
    background: var(--wash-5);
}

.dock {
    position: absolute;
    left: 50%;
    bottom: 15px;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
    width: max-content;
    max-width: calc(100% - 20px);
    min-height: 48px;
    margin: 0;
    padding: 4px;
    border: 1px solid var(--line);
    border-radius: 12px;
    background: var(--material);
    backdrop-filter: var(--glass);
    -webkit-backdrop-filter: var(--glass);
    z-index: 5;
}

.dock-add {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    flex: 0 0 40px;
    padding: 0;
    border: 0;
    border-radius: 10px;
    background: transparent;
    color: var(--muted);
    font: inherit;
    font-size: 15px;
    font-weight: 600;
    line-height: 1;
    transition: background 0.18s ease, color 0.18s ease;
}

.dock-add svg {
    flex-shrink: 0;
    width: 24px;
    height: 24px;
}

.dock-add:hover {
    background: var(--wash-3);
    color: var(--text);
}

.dock-add:active {
    transform: scale(0.94);
}

.dock-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    flex: 0 0 40px;
    padding: 0;
    border: 0;
    border-radius: 10px;
    background: transparent;
    color: var(--muted);
    transition: background 0.18s ease, color 0.18s ease;
}

.dock-btn svg {
    flex-shrink: 0;
    width: 24px;
    height: 24px;
}

.dock-btn:hover {
    background: var(--wash-3);
    color: var(--text);
}

.dock-btn:active {
    transform: scale(0.94);
}

.dock-avatar {
    width: 34px;
    height: 34px;
    font-size: 13px;
    border-radius: 50%;
    box-shadow: none;
}

.dock-pop {
    position: absolute;
    left: 50%;
    bottom: calc(64px + env(safe-area-inset-bottom, 0px));
    transform: translateX(-50%);
    z-index: 8;
    width: 200px;
    padding: 6px;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--material-bright);
    backdrop-filter: var(--glass);
    -webkit-backdrop-filter: var(--glass);
    box-shadow: 0 4px 16px var(--shadow);
    transform-origin: bottom center;
    animation: dock-pop-in 0.16s ease;
}

.dock-pop-item {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    height: 40px;
    padding: 0 10px;
    border: 0;
    border-radius: 9px;
    background: transparent;
    color: var(--text);
    font: inherit;
    font-size: 14px;
    font-weight: 500;
    text-align: left;
}

.dock-pop-item:hover {
    background: var(--wash-3);
}

.dock-pop-item svg {
    flex-shrink: 0;
    color: var(--accent);
}

.ctx-menu {
    position: fixed;
    z-index: 20;
    min-width: 168px;
    padding: 6px;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--material-bright);
    backdrop-filter: var(--glass);
    -webkit-backdrop-filter: var(--glass);
    box-shadow: 0 4px 16px var(--shadow);
    transform-origin: center top;
    animation: pop-in 0.16s ease;
}

.ctx-reactions {
    display: flex;
    gap: 4px;
    padding: 2px 6px;
}

.ctx-menu .ctx-reaction {
    width: 36px;
    height: 36px;
    display: grid;
    place-items: center;
    padding: 0;
    border: 0;
    border-radius: 9px;
    background: transparent;
    font-size: 20px;
    line-height: 1;
    transition: background 0.15s ease, transform 0.15s ease;
}

.ctx-menu .ctx-reaction:hover {
    background: var(--wash-3);
}

.ctx-menu .ctx-reaction:active {
    transform: scale(0.88);
}

.ctx-menu button {
    display: block;
    width: 100%;
    height: 36px;
    padding: 0 10px;
    border: 0;
    border-radius: 9px;
    background: transparent;
    color: var(--text);
    font: inherit;
    font-size: 14px;
    font-weight: 500;
    text-align: left;
}

.ctx-menu button:hover {
    background: var(--wash-3);
}

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

.modal {
    position: absolute;
    inset: 0;
    z-index: 12;
    display: grid;
    place-items: center;
    padding: 20px;
}

#recover-modal {
    position: fixed;
}

/* Экран потери ключа показывается до входа в комнату (вне #room), поэтому, как и окно
   восстановления пароля, фиксирован к окну и перекрывает стартовый экран #boot. */
#key-loss {
    position: fixed;
}

#key-loss-choice {
    display: grid;
    gap: 8px;
}

.modal-backdrop {
    position: absolute;
    inset: 0;
    border: 0;
    /* Затемнения нет: под окном видно приложение — оно размыто тем же стеклом,
       что и панели (--glass), а контраст даёт материал самой карточки. */
    background: transparent;
    backdrop-filter: var(--glass);
    -webkit-backdrop-filter: var(--glass);
}

.modal-card {
    position: relative;
    z-index: 1;
    /* Карточка сама не прокручивается: прокрутка ушла внутрь листа (.sheet).
       Иначе наложение подэкрана, отсчитанное от содержимого карточки, уезжало бы
       вверх вместе с ней — его верхняя точка зависела бы от позиции скролла,
       а не от верха окна. Колонка нужна, чтобы лист получил остаток высоты.
       Отступы карточка не держит: их несёт лист (см. .sheet) — тогда подэкран
       и лист под ним считают отступ от одного края, а полоса прокрутки встаёт
       вплотную к краю карточки, а не внутрь него. */
    display: flex;
    flex-direction: column;
    width: min(360px, 100%);
    max-height: min(560px, calc(100% - 24px));
    overflow: hidden;
    padding: 0;
    border: 1px solid var(--line);
    border-radius: 12px;
    background: var(--material);
    animation: modal-in 0.24s cubic-bezier(0.22, 1, 0.36, 1);
}

/* Кнопка «Закрыть» — экстренный выход, поэтому она должна работать с любого экрана:
   подэкран — наложение позже в разметке, и без z-index он рисовался бы поверх кнопки
   и перехватывал бы нажатие. Плашка той же полупрозрачной заливкой, что остальные
   мелкие кнопки интерфейса (.uin-value-btn, .ghost), а не голый крестик: на стекле
   карточки он читается как часть окна, а не как случайная иконка на контенте.
   Место под него в шапках листов и подэкранов резервирует padding-right. */
.modal-close {
    position: absolute;
    z-index: 2;
    top: 10px;
    right: 10px;
    width: 32px;
    height: 32px;
    display: grid;
    place-items: center;
    border: 1px solid var(--line-soft);
    border-radius: 10px;
    background: var(--wash-2);
    color: var(--muted);
}

.modal-close:hover {
    background: var(--wash-3);
    color: var(--text);
}

.sheet {
    display: grid;
    gap: 14px;
    /* Ритм секции: gap — единственный отступ между блоками, свои margin'ы блокам
       не нужны (иначе у одной кнопки 14px, у другой 18 или 22). Отступ один на все
       секции — и у листа, и у подэкрана (тот наследует этот же): при переходе контент
       не сдвигается ни по горизонтали, ни по вертикали. */
    padding: 15px 20px;
    /* Длинный лист прокручивается сам, а не тянет за собой карточку: наложение
       подэкрана отсчитывается от карточки, поэтому скролл родителя на него не влияет.
       Прокрутка видна только у того листа, что сейчас на экране. */
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
}

.sheet-head {
    display: grid;
    grid-template-columns: 56px minmax(0, 1fr);
    align-items: center;
    gap: 12px;
    padding-right: 28px;
}

/* Аватар в шапке листа: 56px под колонку .sheet-head. Селектор с двумя классами —
   одиночный .avatar объявлен ниже и по порядку перебивал этот размер. */
.avatar.sheet-avatar {
    width: 56px;
    height: 56px;
    font-size: 22px;
}

/* Аватар в листе «Вы» — кнопка: нажатие открывает меню с загрузкой и удалением фото.
   Размер кнопке не задаём: по колонке .sheet-head она растягивается сама, а высоту
   берёт у аватара внутри (56px, на телефоне 68px) — отдельного правила для узкой
   вёрстки не нужно. */
.sheet-avatar-btn {
    display: grid;
    place-items: center;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: none;
    color: inherit;
    cursor: pointer;
}

.sheet-avatar-btn:active {
    transform: scale(0.94);
}

.sheet-avatar-btn:focus-visible {
    outline: 2px solid var(--accent-press);
    outline-offset: 2px;
}

.sheet-copy {
    min-width: 0;
    display: grid;
    gap: 4px;
}

/* UIN собеседника — та же пилюля с иконкой копирования, что в регистрации. В колонке
   листа она по умолчанию растянулась бы на всю ширину, поэтому прижимаем её к началу
   колонки: левый край пилюли встаёт вровень с именем и статусом. */
.sheet-uin {
    justify-self: start;
}

/* Шапка листа «Вы»: аватар отдельной строкой и крупнее, чем в других листах (там он
   в колонке рядом с именем), а под ним — строка с карандашом правки и UIN и ещё ниже,
   в режиме правки, — пара кнопок «Сохранить»/«Отменить». */
.me-hero {
    display: grid;
    gap: 12px;
    justify-items: center;
}

.me-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 8px;
}

/* Круг заметно крупнее, чем у .avatar.sheet-avatar: в этом листе аватар стоит один
   и читается главным элементом карточки. */
.avatar.me-avatar {
    width: 104px;
    height: 104px;
    font-size: 40px;
}

/* В листе «Вы» имени в шапке нет, поэтому UIN — главный элемент строки: пилюля крупнее,
   чем у собеседника, и стоит рядом с карандашом правки. Класс только у своей пилюли,
   так что пилюля собеседника (.sheet-uin выше) остаётся прежней. */
.me-uin {
    gap: 12px;
    padding: 8px 16px 8px 18px;
}

.me-uin .sheet-sub {
    color: var(--text);
    font-size: 17px;
    font-weight: 600;
    /* Номер не переносится по пробелам: пилюля не должна ломаться посередине
       («UIN 280» и «678 754»). */
    white-space: nowrap;
}

/* Иконка копирования растёт вместе с текстом пилюли. */
.me-uin .uin-copy-icon svg {
    width: 20px;
    height: 20px;
}

.sheet-title {
    margin: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 18px;
    font-weight: 650;
    letter-spacing: -0.03em;
}

/* Подэкраны листа «Вы» (разделы профиля и их окна: смена пароля, коды, сундук) —
   наложение поверх родительского экрана, но только когда открыты как подэкран (класс
   .over, его ставит openSubSheet). Без него это обычный лист: тем же листом кодов
   пользуется регистрация, где он открывается из лобби и держит высоту карточки.
   Карточка не прокручивается (прокручивается сам лист или сам подэкран), поэтому верх
   подэкрана всегда стоит на верху окна — как бы ни был прокручен родительский экран.
   Своя прокрутка нужна длинным экранам (сундук с показанной фразой): в низком окне
   без неё нижние кнопки остались бы за краем. Отступ подэкран берёт у .sheet — тот же,
   что у листа под ним, поэтому контент при переходе не сдвигается. Своего фона у
   подэкрана нет: он показывает материал карточки вместе с её размытием, поэтому
   читается как слой того же окна, а не как отдельная непрозрачная панель (сквозь
   прозрачный слой видно и уезжающий родительский экран — переход получается слоистым). */
.sheet-page.over {
    position: absolute;
    inset: 0;
    align-content: start;
    overflow: auto;
    background: transparent;
}

/* Переход в две стороны: подэкран приезжает справа (sheet-push-in), а экран под ним
   уезжает влево (sheet-push-aside). По «Назад» то же в обратную сторону: подэкран
   уезжает вправо (sheet-push-out), а родитель возвращается слева (sheet-push-return).
   Панели в каждой паре обязаны идти с ОДНИМИ И ТЕМИ ЖЕ длительностью и кривой: тогда
   левый край подэкрана в каждый момент совпадает с правым краем родителя и они едут
   как одна лента (как карусель панелей на телефоне, см. .room .sidebar). Со сбитой
   кривой между ними то щель до материала карточки, то нахлёст — переход выглядит
   рваным. Родителю return forwards не нужен: он заканчивает ровно в своём месте,
   а вот aside нужен — пока он скрыт (visibility), он должен оставаться сдвинутым,
   иначе в начале обратного перехода он мигнул бы на своём месте. */
@keyframes sheet-push-in {
    from {
        transform: translateX(100%);
    }
    to {
        transform: none;
    }
}

@keyframes sheet-push-out {
    from {
        transform: none;
    }
    to {
        transform: translateX(100%);
    }
}

@keyframes sheet-push-aside {
    from {
        transform: none;
    }
    to {
        transform: translateX(-100%);
    }
}

@keyframes sheet-push-return {
    from {
        transform: translateX(-100%);
    }
    to {
        transform: none;
    }
}

.sheet-page.over.push-in {
    animation: sheet-push-in 0.26s cubic-bezier(0.22, 1, 0.36, 1);
}

.push-aside {
    animation: sheet-push-aside 0.26s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}

.sheet-page.over.push-out {
    animation: sheet-push-out 0.26s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}

.push-return {
    animation: sheet-push-return 0.26s cubic-bezier(0.22, 1, 0.36, 1);
}

/* Шапка внутреннего экрана: стрелка назад, отступ, название — одной строкой. Справа
   резервируем место под кнопку «Закрыть»: она лежит в углу карточки, а не листа,
   и длинное название иначе заезжало бы под неё. min-width разрешает заголовку
   усечься, а не вылезать из строки. */
.sheet-nav {
    display: flex;
    align-items: center;
    gap: 8px;
    padding-right: 28px;
}

.sheet-nav .sheet-title {
    min-width: 0;
}

.sheet-back {
    flex: 0 0 auto;
    width: 34px;
    height: 34px;
    display: grid;
    place-items: center;
    padding: 0;
    border: 0;
    border-radius: 10px;
    background: transparent;
    color: var(--accent);
    cursor: pointer;
}

.sheet-back:hover {
    background: var(--wash-3);
}

.sheet-sub,
.sheet-status,
.sheet-kicker {
    margin: 0;
    color: var(--muted);
    font-size: 13px;
}

.sheet-kicker {
    font-weight: 600;
}

/* Предупреждение в листах: фраза от сундука — общий ключ ко всей переписке и группам. */
.sheet-warning {
    margin: 0;
    padding: 10px 12px;
    border: 1px solid rgba(255, 69, 58, 0.32);
    border-radius: 10px;
    background: rgba(255, 69, 58, 0.1);
    color: var(--text);
    font-size: 13px;
    line-height: 1.4;
}

/* Фраза от сундука: её переписывают руками, поэтому моноширинный шрифт и
   перенос длинной строки, чтобы слова не уезжали за край листа. */
.vault-phrase {
    margin: 0;
    padding: 8px 10px;
    border-radius: 8px;
    background: var(--wash-2);
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 13px;
    line-height: 1.5;
    letter-spacing: 0.02em;
    color: var(--text);
    word-break: break-word;
}

/* Подтверждение сундука: в предупреждении, кроме текста, лежат ещё фраза и
   кнопки. Раскладываем содержимое столбиком с ровными промежутками — иначе
   кнопки (inline-block по умолчанию) встают рваной ширины друг под другом. */
#vault-phrase {
    display: grid;
    gap: 14px;
}

#vault-phrase > p {
    margin: 0;
}

#vault-phrase > button {
    width: 100%;
    margin-top: 0;
}

/* Фраза и кнопка копирования — одной строкой: иконка и галочка те же, что у UIN
   в регистрации, поэтому копирование узнаётся с первого взгляда. Кнопка тянется
   по высоте фразы: фраза переносится на 2–3 строки, и короткий квадрат рядом с
   ней выглядел бы обрубком. Кнопка в #vault-phrase-row, а не прямым потомком, —
   иначе правило выше растянуло бы её на всю ширину. */
.vault-phrase-row {
    display: flex;
    align-items: stretch;
    gap: 8px;
}

.vault-phrase-row > .vault-phrase {
    flex: 1;
    min-width: 0;
}

.vault-copy {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    width: 38px;
    min-height: 38px;
    border: 0;
    border-radius: 8px;
    background: var(--wash-2);
    cursor: pointer;
    transition: background 0.18s ease;
}

.vault-copy:hover {
    background: var(--wash-3);
}

.vault-copy:active {
    background: var(--wash-4);
}

.vault-copy.copied .uin-copy-icon {
    color: var(--online);
}

.vault-copy.copied .uin-icon-copy {
    display: none;
}

.vault-copy.copied .uin-icon-done {
    display: block;
}

.sheet-form {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 4px;
}

/* Строка «поле + кнопка»: используется там, где лист просит один UIN.
   Явные grid-колонки на строке нужны, чтобы узкая кнопка не растягивалась
   на всю ширину (у grid-элементов stretch — значение по умолчанию). */
.sheet-add {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 96px;
    gap: 8px;
}

.sheet-add > button {
    width: 100%;
    padding: 0 10px;
}

.sheet-form label {
    color: var(--muted);
    font-size: 12px;
    font-weight: 600;
}

.sheet-form input,
.me-field input {
    width: 100%;
    height: 38px;
    padding: 0 10px;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--fill);
    color: var(--text);
    font: inherit;
}

.sheet-form input:focus,
.sheet-form input:focus-visible,
.me-field input:focus,
.me-field input:focus-visible {
    outline: none;
    border: 1px solid rgba(10, 132, 255, 0.5);
    box-shadow: 0 0 0 3px rgba(10, 132, 255, 0.16);
}

.sheet-form .error {
    margin: 0;
    font-size: 13px;
}

/* Кнопки форм выравниваются по ширине листа, как .sheet-action и .sheet-logout:
   тогда левый и правый край всех элементов листа совпадают. В строке .sheet-add
   кнопка живёт в своей колонке и потому остаётся узкой, справа от поля.
   Селектор — только прямые кнопки формы и кнопка строки .sheet-add: кнопки шапки листа
   (правка анкеты) лежат в своей обёртке, и padding 0 16px сузил бы им содержимое. */
.sheet-form > button,
.sheet-add > button {
    justify-self: stretch;
}

.sheet-form > button,
.sheet-add > button,
.sheet-action {
    height: 38px;
    padding: 0 16px;
    border: 0;
    border-radius: 10px;
    background: var(--accent);
    color: #fff;
    font: inherit;
    font-size: 14px;
    font-weight: 600;
}

.sheet-logout {
    width: 100%;
    height: 38px;
    border: 0;
    border-radius: 10px;
    background: transparent;
    color: var(--danger);
    font: inherit;
    font-size: 14px;
    font-weight: 600;
}

.sheet-actions {
    display: grid;
    gap: 8px;
}

/* Вторая кнопка листа — менее важное действие: заливка карточки вместо акцента. */
.sheet-action.sheet-action-alt {
    background: var(--fill-2);
    color: var(--text);
}

.me-fields {
    display: grid;
    gap: 14px;
}

/* Блок анкеты — подпись и поле. Внутренний отступ (6px) меньше, чем между блоками
   (14px), поэтому «Имя», «Пол» и «Дата рождения» читаются отдельными строками. */
.me-field {
    display: grid;
    gap: 6px;
}

.me-fields .error {
    margin: 0;
    font-size: 13px;
}

/* По умолчанию анкета только для чтения: это карточка профиля, а не форма. Текст
   приглушён, отклика на фокус нет — поле не приглашает нажать. */
.me-field input[readonly] {
    color: var(--muted);
    cursor: default;
}

.me-field input[readonly]:focus,
.me-field input[readonly]:focus-visible {
    border: 1px solid var(--line);
    box-shadow: none;
}

/* Выключенные пола и даты в режиме просмотра: вид тот же, но без подсветки при наведении. */
.sheet-select-value:disabled,
.date-value:disabled {
    color: var(--muted);
    cursor: default;
}

.sheet-select-value:disabled:hover,
.date-value:disabled:hover {
    background: var(--fill);
}

.sheet-select-value:disabled .sheet-select-icon,
.date-value:disabled .date-value-icon {
    color: var(--muted-soft);
}

/* Правка анкеты (переключает setMeEditMode): карандаш стоит в строке рядом с UIN, а
   «Сохранить» и «Отменить» появляются строкой под ней — в одной строке пара кнопок
   не помещается и переносилась над номером. */
.me-edit-actions {
    justify-self: stretch;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
}

/* Кнопки правки ниже остальных кнопок листа (38px) и с полем уже: это пара при
   пилюле, а не главное действие листа. */
.me-edit-btn {
    height: 34px;
    padding: 0 12px;
}

.me-edit-btn:disabled {
    opacity: 0.6;
    cursor: default;
}

/* Карандаш — квадратная кнопка той же высоты, что пилюля UIN, и той же заливкой,
   что мелкие кнопки интерфейса: в строке они читаются парой. */
.me-pencil {
    flex: 0 0 auto;
    width: 38px;
    height: 38px;
    display: grid;
    place-items: center;
    padding: 0;
    border: 0;
    border-radius: 10px;
    background: var(--wash-2);
    color: var(--accent);
    transition: background 0.18s ease, transform 0.16s ease;
}

.me-pencil:hover {
    background: var(--wash-3);
}

.me-pencil:active {
    transform: scale(0.94);
}

.me-pencil:focus-visible {
    outline: 2px solid var(--accent-press);
    outline-offset: 2px;
}

.me-menu {
    display: grid;
    gap: 6px;
}

.me-menu-group {
    display: grid;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--wash-2);
    overflow: hidden;
}

.me-menu-row {
    height: 40px;
    padding: 0 12px;
    border: 0;
    background: transparent;
    color: var(--text);
    font: inherit;
    font-size: 14px;
    font-weight: 600;
    text-align: left;
}

.me-menu-row + .me-menu-row {
    border-top: 1px solid var(--line);
}

.me-menu-row:hover {
    background: var(--wash-3);
}

/* Строка меню со стрелкой: так помечены пункты, которые открывают свой экран. */
.me-menu-row.forward {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

.me-menu-arrow {
    flex: 0 0 auto;
    color: var(--muted-soft);
}

/* Переключатель темы в профиле: три сегмента на дорожке — та же схема, что у
   переключателя «Контакты/Группы» в боковой колонке. */
.theme-switch {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 3px;
    padding: 3px;
    border-radius: 8px;
    background: var(--track);
}

.theme-option {
    height: 34px;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--muted);
    /* Три сегмента делят дорожку листа: 14px в них ещё влезают, но на узких
       телефонах слово «Системная» переносилось бы — удерживаем одну строку. */
    white-space: nowrap;
    font: inherit;
    font-size: 14px;
    font-weight: 600;
    transition: background 0.18s ease, color 0.18s ease;
}

.theme-option.active {
    background: var(--seg-active);
    color: var(--seg-text);
}

/* Анкета в листе профиля: пол, дата рождения и приватность (аватар меняется кнопкой
   в шапке листа). */
.me-profile {
    display: grid;
    gap: 6px;
}

/* Пол — свой выпадающий список (js/selectmenu.js) в общем языке полей листа: поле
   повторяет вид текстовых полей (.sheet-form input) и поля даты (.date-value) — те же
   высота, рамка, скругление и заливка, — а раскрытый список идёт той же панелью, что
   календарь (.date-picker). Нативный <select> не годится: его список рисует система. */
.sheet-select {
    display: grid;
}

.sheet-select-value {
    width: 100%;
    height: 38px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 0 10px;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--fill);
    color: var(--text);
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.sheet-select-value:hover {
    background: var(--fill-2);
}

/* Тот же отклик на фокус, что у текстовых полей листа: поле списка не внутри .sheet-form. */
.sheet-select-value:focus,
.sheet-select-value:focus-visible {
    outline: none;
    border: 1px solid rgba(10, 132, 255, 0.5);
    box-shadow: 0 0 0 3px rgba(10, 132, 255, 0.16);
}

.sheet-select-text.placeholder {
    color: var(--muted);
}

.sheet-select-icon {
    flex: 0 0 auto;
    color: var(--muted);
    transition: color 0.18s ease, transform 0.18s ease;
}

/* Раскрытый список разворачивает стрелку вверх: видно, что панель открыта. */
.sheet-select-value.open .sheet-select-icon {
    color: var(--accent);
    transform: rotate(180deg);
}

/* Панель списка — тот же вид, что у календаря даты рождения: она раскрывается под
   полем, внутри листа, а не отдельным окном системы. */
.sheet-menu {
    display: grid;
    gap: 3px;
    margin-top: 8px;
    padding: 8px;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--wash-2);
    animation: pop-in 0.16s ease;
}

/* Строка списка: выбранная — та же плашка, что у активного сегмента переключателей
   темы и «Контакты/Группы». */
.sheet-menu-option {
    height: 34px;
    padding: 0 10px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--text);
    font: inherit;
    font-size: 14px;
    font-weight: 600;
    text-align: left;
    cursor: pointer;
    transition: background 0.18s ease, color 0.18s ease;
}

.sheet-menu-option:hover {
    background: var(--wash-3);
}

.sheet-menu-option.selected {
    background: var(--seg-active);
    color: var(--seg-text);
}

.sheet-menu-option:focus-visible {
    outline: 2px solid var(--accent-press);
    outline-offset: 2px;
}

/* Дата рождения — кнопка, а не <input type="date">: нативное поле рисует браузер
   своим виджетом, и в лист он не вписывался. Вид и размеры взяты у текстовых полей
   листа (.sheet-form input) — вместе они читаются как один набор полей: тот же шрифт
   (font: inherit), высота, рамка, скругление и заливка. */
.date-field {
    display: grid;
}

.date-value {
    width: 100%;
    height: 38px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 0 10px;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--fill);
    color: var(--text);
    font: inherit;
    text-align: left;
}

.date-value:hover {
    background: var(--fill-2);
}

/* Пустое поле — приглушённо, как placeholder у текстового поля. */
.date-value-text.placeholder {
    color: var(--muted);
}

.date-value-icon {
    flex: 0 0 auto;
    color: var(--muted);
    transition: color 0.18s ease;
}

.date-value.open .date-value-icon {
    color: var(--accent);
}

/* Календарь раскрывается под полем, внутри листа, а не отдельным окном: так он
   едет вместе с содержимым, когда лист прокручивают, и не спорит с модалкой. */
.date-picker {
    display: grid;
    gap: 6px;
    margin-top: 8px;
    padding: 8px;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--wash-2);
    animation: pop-in 0.16s ease;
}

.date-picker-head {
    display: grid;
    grid-template-columns: 32px minmax(0, 1fr) 32px;
    align-items: center;
    gap: 6px;
}

.date-nav,
.date-title {
    height: 32px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--text);
    font: inherit;
}

.date-nav {
    display: grid;
    place-items: center;
    color: var(--muted);
}

.date-title {
    font-size: 14px;
    font-weight: 650;
}

.date-nav:hover:not(:disabled),
.date-title:hover {
    background: var(--wash-3);
}

.date-nav:disabled {
    opacity: 0.3;
}

.date-week,
.date-grid {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: 2px;
}

.date-week span {
    color: var(--muted);
    font-size: 11px;
    font-weight: 600;
    text-align: center;
}

/* Месяцы и годы — та же ячейка, только шире: 4 колонки вместо 7. */
.date-grid-months,
.date-grid-years {
    grid-template-columns: repeat(4, minmax(0, 1fr));
}

.date-cell {
    height: 32px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--text);
    font: inherit;
    font-size: 13px;
    font-weight: 500;
}

.date-cell:hover:not(:disabled):not(.selected) {
    background: var(--wash-3);
}

/* Сегодня — только рамка: заливка занята выбранным днём, и два залитых дня
   в одной сетке читались бы как два выбора. */
.date-cell[aria-current="date"] {
    box-shadow: inset 0 0 0 1px var(--accent);
}

/* Выбранный день — акцент, текст поверх него белый: так же залит свой пузырь. */
.date-cell.selected {
    background: var(--accent);
    color: #fff;
}

.date-cell:disabled {
    opacity: 0.3;
}

/* Пустая ячейка держит место в сетке, но не ловит наведение и клики. */
.date-cell-empty {
    visibility: hidden;
}

.date-picker-foot {
    display: flex;
    justify-content: flex-end;
}

.date-clear {
    height: 30px;
    padding: 0 10px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--danger);
    font: inherit;
    font-size: 13px;
    font-weight: 600;
}

.date-clear:hover {
    background: var(--wash-3);
}

/* Переключатель «скрывать онлайн»: строка меню с дорожкой и ползунком справа. */
.switch-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

.switch-track {
    flex: 0 0 auto;
    width: 42px;
    height: 24px;
    padding: 3px;
    border-radius: 999px;
    background: var(--track);
    transition: background 0.18s ease;
}

.switch-knob {
    display: block;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
    transition: transform 0.18s ease;
}

#me-hide-online.on .switch-track {
    background: var(--accent);
}

#me-hide-online.on .switch-knob {
    transform: translateX(18px);
}

/* Анкета контакта: строки «поле — значение», заполнены только известные поля. */
.profile-list {
    display: grid;
    gap: 6px;
    margin: 0;
    padding: 10px 12px;
    border: 1px solid var(--line);
    border-radius: 10px;
    background: var(--wash-2);
}

.profile-row {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: 10px;
    font-size: 13px;
}

.profile-row dt {
    color: var(--muted);
}

.profile-row dd {
    margin: 0;
    color: var(--text);
    font-weight: 600;
    overflow-wrap: anywhere;
}

.codes-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6px;
    max-height: 200px;
    overflow: auto;
}

.codes-list-item {
    padding: 8px 10px;
    border-radius: 10px;
    background: var(--well);
    border: 0.5px solid var(--line);
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 13px;
    letter-spacing: 0.02em;
    text-align: center;
    color: var(--text);
    word-break: break-all;
}

.codes-empty {
    grid-column: 1 / -1;
    font-family: inherit;
    font-size: 13px;
    color: var(--muted);
    line-height: 1.4;
    text-align: left;
    word-break: normal;
}

#codes-regenerate {
    background: var(--well);
    color: var(--accent);
}

.sheet-people {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    /* Строки должны оставаться по высоте контента: без этого единственная строка
       растягивается на всю высоту списка и контакт «висит» по центру. */
    align-content: start;
    gap: 2px;
    max-height: 280px;
    overflow: auto;
}

#contacts-list li,
#group-members li {
    display: flex;
    align-items: center;
    gap: 8px;
}

.sheet-people button {
    display: grid;
    grid-template-columns: 36px minmax(0, 1fr);
    align-items: center;
    gap: 10px;
    width: 100%;
    min-height: 48px;
    padding: 6px 8px;
    border: 0;
    border-radius: 10px;
    background: transparent;
    color: inherit;
    text-align: left;
    font: inherit;
}

.sheet-people button:hover {
    background: var(--wash-2);
}

.sheet-people .contact-remove {
    width: 28px;
    height: 28px;
    min-height: 28px;
    display: grid;
    grid-template-columns: none;
    gap: 0;
    place-items: center;
    flex-shrink: 0;
    padding: 0;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--muted);
    transition: background 0.15s ease, color 0.15s ease;
}

.contact-remove:hover {
    background: rgba(255, 69, 58, 0.14);
    color: var(--danger);
}

.sheet-people .person-status {
    display: block;
    color: var(--muted);
    font-size: 12px;
}

.sheet-people .person-name {
    font-size: 14px;
    font-weight: 600;
}

.sidebar-title {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin: 0;
    padding: 8px 16px 4px;
    color: var(--muted);
    font-size: 13px;
    font-weight: 600;
    letter-spacing: -0.02em;
}

.chat-switch {
    display: grid;
    grid-template-columns: 40px minmax(0, 1fr) auto;
    align-items: center;
    gap: 10px;
    width: calc(100% - 16px);
    min-height: 56px;
    margin: 6px 8px 4px;
    padding: 8px 10px;
    border: 0;
    border-radius: 10px;
    background: var(--wash-1);
    color: inherit;
    text-align: left;
    font: inherit;
    font-weight: 500;
    letter-spacing: -0.02em;
}

.chat-switch:hover,
#roster .person:hover,
#groups .person:hover {
    background: var(--wash-3);
}

.chat-switch.active,
#roster li.active .person,
#groups li.active .person {
    background: rgba(10, 132, 255, 0.18);
}

.chat-avatar {
    color: #fff;
    background: var(--accent);
}

.unread {
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    display: grid;
    place-items: center;
    border-radius: 8px;
    background: var(--accent);
    color: #fff;
    font-size: 11px;
    font-weight: 700;
    line-height: 1;
    font-variant-numeric: tabular-nums;
}

.thread-bar {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-shrink: 0;
    position: fixed;
    top: 0;
    z-index: 100;
    width: -webkit-fill-available;
    margin: 0;
    padding: 10px 12px;
    border: 1px solid var(--line);
    border-top: 0;
    border-radius: 12px;
    border-top-left-radius: 0;
    border-top-right-radius: 0;
    background: var(--panel);
    backdrop-filter: var(--glass);
    -webkit-backdrop-filter: var(--glass);
}

.thread-info,
.thread-idle {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
    flex: 1;
    padding: 0;
    border: 0;
    background: transparent;
    color: inherit;
    text-align: left;
    font: inherit;
}

.thread-info {
    border-radius: 10px;
}

.thread-info:hover {
    background: var(--wash-1);
}

#thread-back {
    width: 36px;
    height: 36px;
    display: grid;
    place-items: center;
    flex-shrink: 0;
    border: 0;
    border-radius: 10px;
    background: transparent;
    color: var(--text);
}

#thread-back:hover {
    background: var(--wash-2);
}

#thread-back:active {
    transform: scale(0.9);
}

/* Кнопка звонка — в шапке личного чата, прижата вправо (соседние .thread-info
   и .thread-idle растянуты flex: 1). */
.thread-call {
    width: 36px;
    height: 36px;
    display: grid;
    place-items: center;
    flex-shrink: 0;
    border: 0;
    border-radius: 10px;
    background: transparent;
    color: var(--text);
}

.thread-call:hover {
    background: var(--wash-2);
}

.thread-call:active {
    transform: scale(0.9);
}

.thread-bar .avatar {
    width: 36px;
    height: 36px;
    font-size: 14px;
}

.thread-copy,
.person-copy {
    min-width: 0;
    display: grid;
    gap: 1px;
}

.thread-name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 15px;
    font-weight: 600;
    letter-spacing: -0.03em;
}

.thread-status,
.person-status {
    color: var(--online);
    font-size: 12px;
    font-weight: 500;
    letter-spacing: -0.01em;
}

.thread-status.offline,
.person-status.offline {
    color: var(--muted);
}

#roster li.self .person-status {
    color: var(--muted);
}

#peer-count {
    color: var(--text);
    font-variant-numeric: tabular-nums;
}

.thread {
    position: relative;
    display: flex;
    flex-direction: column;
    height: 100%;
    min-width: 0;
    min-height: 0;
    overflow: hidden;
}

.room-empty {
    position: absolute;
    inset: 56px 0 56px 0;
    z-index: 1;
    display: grid;
    place-content: center;
    justify-items: center;
    gap: 8px;
    padding: 24px;
    pointer-events: none;
    text-align: center;
}

.room.idle .room-empty {
    inset: 56px 0 0 0;
}

.room-empty p {
    margin: 0;
}

.room-empty p:first-child {
    color: var(--text);
    font-size: 15px;
    font-weight: 600;
    letter-spacing: -0.02em;
    line-height: 1.2;
}

.room-empty p:last-child {
    color: var(--muted);
    font-size: 13px;
    line-height: 1.4;
    letter-spacing: -0.015em;
}

.messages {
    position: relative;
    z-index: 2;
    flex: 1;
    min-height: 0;
    overflow: auto;
    display: flex;
    flex-direction: column;
    gap: 0;
    /* Панель ввода фиксированная и растёт под текст, поэтому её высоту пишет
       main.js в --composer-space: иначе лента уходила бы под неё. */
    padding: 60px 14px var(--composer-space, 52px);
    overflow-anchor: none;
}

.chat-end {
    height: 4px;
    flex: 0 0 4px;
    pointer-events: none;
}

.day-chip {
    align-self: center;
    margin: 12px 0 8px;
    padding: 4px 10px;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--well);
    color: var(--muted);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.msg-row {
    display: flex;
    align-items: flex-end;
    gap: 0;
    width: fit-content;
    max-width: min(78%, 42rem);
    align-self: flex-start;
    animation: msg-in 0.22s ease;
}

/* Свои сообщения — справа */
.msg-row.mine {
    align-self: flex-end;
    gap: 8px;
}

/* Удаление сообщения: пузырь гаснет, затем строка складывается по высоте, чтобы
   нижние сообщения подтянулись вверх, а не прыгнули мгновенно. Высоту строка
   получает инлайном (замер перед анимацией), поэтому здесь нужен !important. */
.msg-row.removing {
    height: 0 !important;
    overflow: hidden;
    pointer-events: none;
    animation: msg-out 0.2s ease forwards;
    transition: height 0.24s ease 0.06s;
}

.msg-row .msg-avatar {
    flex: 0 0 auto;
    align-self: flex-end;
    margin: auto;
}

.msg-row.mine .msg-avatar {
    display: none;
}

.msg-row .msg-avatar .avatar {
    width: 30px;
    height: 30px;
    font-size: 12px;
}

.msg-ref {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    min-width: 0;
    max-width: 100%;
}

/* Связка сообщений: прижимаем пузыри одного автора подряд (как в iMessage) */
.msg-group {
    margin-top: 3px;
}

.msg-row {
    position: relative;
}

.msg {
    position: relative;
    min-width: 0;
    width: fit-content;
    max-width: 100%;
    padding: 8px 13px 8px;
    border: 1px solid var(--line);
    border-radius: 12px;
    background: var(--panel);
    backdrop-filter: var(--glass);
    -webkit-backdrop-filter: var(--glass);
    box-shadow: 0 1px 1px var(--shadow);
    color: var(--bubble-text);
    font-size: 15px;
    line-height: 1.35;
    letter-spacing: -0.016em;
}

.msg-row.mine .msg {
    border-color: transparent;
    background: var(--bubble-mine);
    color: #fff;
}

.msg-meta {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    margin: 0 0 3px;
}

.msg-meta b {
    cursor: pointer;
}

.msg p {
    margin: 0;
    overflow-wrap: anywhere;
    font-family: var(--font);
}

/* Зашифрованное сообщение, ключа которого нет на этом устройстве: помечаем
   явно, иначе пузырь выглядит просто пустым (как сломанная вёрстка). */
.msg p.msg-locked {
    color: var(--muted);
    font-style: italic;
}

.msg-row.mine .msg p.msg-locked {
    color: rgba(255, 255, 255, 0.6);
}

.msg b {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--name, #64d2ff);
    font-size: 13px;
    font-weight: 700;
    letter-spacing: -0.02em;
    cursor: pointer;
}

.msg-row.mine .msg b {
    cursor: default;
}

/* Время вынесено за пределы пузыря: свои — слева внизу, чужие — справа внизу */
.msg-time {
    flex-shrink: 0;
    align-self: flex-end;
    color: var(--muted-soft);
    font-size: 11px;
    font-weight: 500;
    letter-spacing: 0.02em;
    line-height: 1;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    margin: 0 0 2px;
}

.msg-row.mine .msg-time {
    margin: auto;
}

.msg-row:not(.mine) .msg-time {
    margin: auto;
}

/* В связке подряд идущих сообщений время показываем только у последнего */
.msg-row:has(+ .msg-row.msg-group) .msg-time {
    display: none;
}

.msg-media {
    display: block;
    max-width: min(320px, 100%);
    max-height: 260px;
    border-radius: 8px;
    margin-bottom: 4px;
}
.msg-audio {
    display: block;
    max-width: 260px;
    height: 36px;
    border-radius: 8px;
}

/* Медиа-сообщение без текста: никто не „лезет“ в углы */
.msg.media-only {
    padding: 7px 10px;
    border-radius: 10px;
}

.msg.media-only .msg-media {
    border-radius: 8px;
    margin-bottom: 0;
}

/* Голосовые: контент внутри дива, отступы заданы на артикле */
.voice-player {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 220px;
    max-width: 300px;
    border-radius: 8px;
}

.voice-play {
    flex: 0 0 auto;
    width: 34px;
    height: 34px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 50%;
    background: var(--accent);
    color: #fff;
    cursor: pointer;
    transition: background 0.18s ease, transform 0.16s ease;
}

.voice-play:hover {
    background: var(--accent-press);
}

.voice-play:active {
    transform: scale(0.92);
}

.voice-play[aria-pressed="true"] {
    background: var(--danger);
}

.msg-row.mine .voice-play {
    background: #fff;
    color: var(--accent);
}

.msg-row.mine .voice-play:hover {
    background: rgba(255, 255, 255, 0.88);
}

.msg-row.mine .voice-play[aria-pressed="true"] {
    background: #fff;
    color: var(--danger);
}

.voice-wave {
    position: relative;
    flex: 1 1 auto;
    min-width: 0;
    height: 34px;
    display: flex;
    align-items: center;
    gap: 2px;
    cursor: pointer;
}

.voice-wave i {
    position: relative;
    z-index: 1;
    display: block;
    width: 3px;
    border-radius: 2px;
    background: var(--wave);
    transition: background 0.15s ease;
}

.msg-row.mine .voice-wave i {
    background: rgba(255, 255, 255, 0.4);
}

.voice-wave i.progress {
    background: var(--accent);
}

.msg-row.mine .voice-wave i.progress {
    background: #fff;
}

.voice-time {
    flex: 0 0 auto;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.02em;
    font-variant-numeric: tabular-nums;
    color: var(--muted);
    min-width: 38px;
    text-align: right;
}

.msg-row.mine .voice-time {
    color: rgba(255, 255, 255, 0.85);
}

.voice-player .msg-audio {
    display: none;
}


/* Связка подряд идущих сообщений: уменьшаем внешние отступы. Зазор задаётся
   только отступом сверху — нижних margin'ов у строк нет, иначе во flex-ленте
   (margin'ы не схлопываются) чужие пузыри получали бы лишние 12px. */
.msg-row + .msg-row.msg-group {
    margin-top: 3px;
}

.msg-row.msg-group + .msg-row.msg-group {
    margin-top: 3px;
}

.msg-row + .msg-row:not(.msg-group) {
    margin-top: 12px;
}

.msg-row.msg-group + .msg-row {
    margin-top: 12px;
}

/* Аватар у чужих сообщений */
.msg-row:not(.mine) {
    gap: 8px;
    margin: 0;
}

/* Аватар — только у последнего сообщения в связке (как в iMessage) */
/* У не-последних (после них связка продолжается) аватар скрыт */
.msg-row:not(.mine):has(+ .msg-row:not(.mine).msg-group) .msg-avatar {
    opacity: 0;
    visibility: hidden;
}

/* Имя — только у ПЕРВОГО сообщения в связке: у тех, перед кем есть чужой msg-group, скрываем */
.msg-row:not(.mine) + .msg-row:not(.mine).msg-group .msg-meta {
    display: none;
}

/* Реакции: компактный блок под пузырём (чужие — слева, свои — справа) */
.msg-reactions {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin-top: 5px;
    padding: 0 2px;
    width: fit-content;
}

.msg-row.mine .msg-reactions {
    margin-left: auto;
    justify-content: flex-end;
}
.reaction-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    height: 24px;
    padding: 0 9px;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: var(--fill-2);
    color: var(--text);
    font: inherit;
    font-size: 12px;
    font-weight: 600;
    line-height: 1;
    transform-origin: center bottom;
    transition: background 0.15s ease;
}

.reaction-chip:hover {
    background: var(--fill-3);
}

.reaction-chip:active {
    transform: scale(0.9);
}

/* Реакция только что появилась — «выпрыгивает» снизу с лёгким перелётом. */
.reaction-chip.fresh {
    animation: reaction-in 0.26s cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Счётчик у уже стоя́щей реакции: короткий «толчок», когда добавили свою. */
.reaction-chip.bump {
    animation: reaction-bump 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.reaction-chip .reaction-count {
    font-variant-numeric: tabular-nums;
}



/* Карандаш «сообщение изменено» — рядом со временем (снаружи пузыря) */
.msg-edited {
    flex-shrink: 0;
    align-self: flex-end;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--muted-soft);
    margin: 0 0 1px;
}

.msg-edited svg {
    width: 11px;
    height: 11px;
    display: block;
}

/* У своих зеркалим входящие: карандаш с 4px от пузыря (как у чужих с другой стороны),
   цвет — базовый приглушённый, как у времени/карандаша входящих */
.msg-row.mine .msg-edited {
    margin: auto 4px auto 0;
}

.msg-row:not(.mine) .msg-edited {
    margin: auto 0 auto 4px;
}

/* Микрофон и «отправить» делят последнюю клетку: видима всегда ровно одна из
   двух кнопок (пустое поле — микрофон, есть текст — отправка), поэтому строка
   не перекраивается при вводе. Клетку задаём строкой и колонкой сразу: при
   одном только grid-column авторазмещение унесло бы вторую кнопку во вторую
   строку и панель стала бы вдвое выше. */
.chat-form {
    display: grid;
    grid-template-columns: 34px minmax(0, 1fr) 34px;
    gap: 4px;
    align-items: center;
}

.composer .chat-form:disabled {
    opacity: 0.55;
}

/* Поле ввода — та же плоскость, что панель: своего фона и рамки у него нет.
   Поле многострочное: текст переносится, а высоту под содержимое считает
   main.js (потолок задаёт max-height, дальше — прокрутка внутри поля). */
.chat-form textarea {
    min-height: 36px;
    max-height: 116px;
    padding: 8px 14px;
    line-height: 20px;
    border: 0;
    background: none;
    resize: none;
    overflow-y: auto;
    /* Длинные ссылки и слова без пробелов переносим, а не прокручиваем. */
    overflow-wrap: break-word;
    grid-column: 2;
}

.chat-form button[type="submit"] {
    width: 34px;
    height: 34px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 50%;
    background: var(--accent);
    color: #fff;
    transition: background 0.18s ease, transform 0.16s ease;
    grid-row: 1;
    grid-column: 3;
}

.chat-form button[type="submit"]:hover {
    background: var(--accent-press);
}

.chat-form button[type="submit"]:active {
    transform: scale(0.9);
}

.emoji-toggle {
    width: 34px;
    height: 34px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--muted);
    transition: background 0.18s ease, color 0.18s ease, transform 0.16s ease;
    grid-column: auto;
}

.emoji-toggle:hover {
    background: var(--wash-2);
    color: var(--text);
}

.emoji-toggle:active {
    transform: scale(0.9);
}

.emoji-toggle[aria-pressed="true"] {
    background: rgba(10, 132, 255, 0.18);
    color: var(--text);
}

#attach-voice {
    grid-row: 1;
    grid-column: 3;
}

#attach-voice.recording {
    background: rgba(255, 69, 58, 0.2);
    color: var(--danger);
}

.composer {
    position: fixed;
    bottom: 0;
    z-index: 100;
    width: -webkit-fill-available;
    margin: 0;
    padding: 6px;
    border: 1px solid var(--line);
    border-bottom: 0;
    border-radius: 12px;
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
    background: var(--panel);
    backdrop-filter: var(--glass);
    -webkit-backdrop-filter: var(--glass);
}

.composer > .voice-rec {
    display: flex;
    align-items: center;
    gap: 10px;
    height: 36px;
    min-width: 0;
}

.voice-rec {
    color: var(--danger);
    overflow: hidden;
}

.voice-rec-stop {
    flex: 0 0 auto;
    margin-left: auto;
    width: 30px;
    height: 30px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 50%;
    background: rgba(255, 69, 58, 0.2);
    color: var(--danger);
    cursor: pointer;
    transition: background 0.16s ease, transform 0.16s ease;
}

.voice-rec-stop:hover {
    background: rgba(255, 69, 58, 0.34);
}

.voice-rec-stop:active {
    transform: scale(0.88);
}

.voice-rec-wave {
    flex: 1 1 auto;
    min-width: 0;
    height: 22px;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 2px;
}

.voice-rec-wave i {
    display: block;
    width: 3px;
    border-radius: 2px;
    background: var(--danger);
    height: 4px;
    transition: height 80ms linear, opacity 0.3s ease;
}

.voice-rec-timer {
    flex: 0 0 auto;
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 0.02em;
    font-variant-numeric: tabular-nums;
    color: var(--text);
    min-width: 34px;
    text-align: right;
}

.voice-rec-hint {
    flex: 0 0 auto;
    font-size: 11px;
    font-weight: 500;
    letter-spacing: 0.03em;
    color: var(--muted);
}

.voice-rec-send {
    flex: 0 0 auto;
    width: 34px;
    height: 34px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 50%;
    background: var(--accent);
    color: #fff;
    cursor: pointer;
    transition: background 0.16s ease, transform 0.16s ease;
}

.voice-rec-send:hover {
    background: var(--accent-press);
}

.voice-rec-send:active {
    transform: scale(0.9);
}

.emoji-picker {
    position: absolute;
    left: 5px;
    right: 5px;
    /* Пикер лежит вне панели и отсчитывается от низа треда, поэтому высоту
       панели берём из той же переменной: панель растёт под многострочный ввод,
       и прежнее фиксированное число накрыло бы поле ввода. */
    bottom: calc(var(--composer-space, 51px) + 12px);
    z-index: 6;
    width: min(352px, calc(100% - 10px));
    border: 1px solid var(--line);
    border-radius: 12px;
    background: var(--material-bright);
    backdrop-filter: var(--glass);
    -webkit-backdrop-filter: var(--glass);
    box-shadow: 0 4px 16px var(--shadow);
    transform-origin: bottom center;
    animation: emoji-pop-in 0.22s cubic-bezier(0.25, 1.2, 0.35, 1);
}

@keyframes emoji-pop-in {
    from {
        opacity: 0;
        transform: translateY(8px) scale(0.95);
    }
    to {
        opacity: 1;
        transform: translateY(0) scale(1);
    }
}

/* В мобильной вёрстке пикер центрируется через translateX(-50%), поэтому
   кадры обязаны нести тот же сдвиг: иначе анимация затирает центрирование
   и панель на открытии прыгает вбок. */
@keyframes emoji-rise-in {
    from {
        opacity: 0;
        transform: translateX(-50%) translateY(10px);
    }
    to {
        opacity: 1;
        transform: translateX(-50%) translateY(0);
    }
}
.emoji-grid {
    display: grid;
    grid-template-columns: repeat(8, 1fr);
    gap: 2px;
}

.emoji-grid button {
    height: 34px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    font-size: 22px;
    line-height: 1;
    font-family: var(--font);
    transition: background 0.15s ease, transform 0.15s ease;
}

.emoji-grid button:hover {
    background: var(--wash-3);
}

.emoji-grid button:active {
    transform: scale(0.86);
}

.toasts {
    position: absolute;
    top: 16px;
    left: calc(var(--sidebar) + (100% - var(--sidebar)) / 2);
    z-index: 95;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
    width: min(360px, calc(100% - var(--sidebar) - 32px));
    transform: translateX(-50%);
    pointer-events: none;
}

.toast {
    display: grid;
    grid-template-columns: 32px 1fr;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 10px 12px;
    border: 1px solid var(--line);
    border-radius: 12px;
    background: var(--material);
    backdrop-filter: var(--glass);
    -webkit-backdrop-filter: var(--glass);
    box-shadow: 0 4px 16px var(--shadow);
    color: var(--text);
    text-align: left;
    pointer-events: auto;
    animation: toast-in 0.28s ease;
}

.toast.out {
    animation: toast-out 0.2s ease forwards;
}

.toast-avatar {
    width: 32px;
    height: 32px;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: var(--name, var(--accent));
    font-size: 13px;
    font-weight: 600;
}

.toast-copy {
    min-width: 0;
}

.toast-copy b {
    display: block;
    font-size: 13px;
    font-weight: 600;
    letter-spacing: -0.02em;
}

.toast-copy p {
    margin: 1px 0 0;
    overflow: hidden;
    color: var(--muted);
    font-size: 13px;
    line-height: 1.3;
    text-overflow: ellipsis;
    white-space: nowrap;
}

#roster {
    list-style: none;
    margin: 0;
    padding: 0 0 8px;
    flex: 1;
    min-height: 0;
    overflow: auto;
    display: grid;
    align-content: start;
    gap: 0;
}

#groups {
    list-style: none;
    margin: 0;
    padding: 0 0 8px;
    flex: 1;
    min-height: 0;
    overflow: auto;
    display: grid;
    align-content: start;
    gap: 0;
}

#roster li,
#groups li {
    min-width: 0;
}

#roster li.self {
    display: grid;
    grid-template-columns: 40px minmax(0, 1fr);
    align-items: center;
    gap: 10px;
    min-height: 52px;
    padding: 6px 12px;
    border: 0;
    border-radius: 0;
    opacity: 1;
    font-size: 15px;
    font-weight: 500;
    letter-spacing: -0.02em;
}

#roster .person,
#groups .person {
    display: grid;
    grid-template-columns: 40px minmax(0, 1fr) auto;
    align-items: center;
    gap: 10px;
    width: calc(100% - 16px);
    min-height: 52px;
    margin: 1px 8px;
    padding: 6px 10px;
    border: 0;
    border-radius: 10px;
    background: transparent;
    color: inherit;
    text-align: left;
    font: inherit;
    font-size: 15px;
    font-weight: 500;
    letter-spacing: -0.02em;
}

.avatar-wrap {
    position: relative;
    width: 36px;
    height: 36px;
    flex-shrink: 0;
}

.avatar {
    width: 36px;
    height: 36px;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: var(--name, var(--accent));
    font-size: 14px;
    font-weight: 600;
}

/* Аватар-картинка из профиля: та же форма, что у буквы-заглушки, поэтому
   заполняет .avatar целиком и не зависит от размера конкретного места. */
.avatar-img {
    display: block;
    width: 100%;
    height: 100%;
    border-radius: 50%;
    object-fit: cover;
}

.person-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.chat-form.disabled {
    opacity: 0.55;
    pointer-events: none;
}

@keyframes rise-in {
    from {
        opacity: 0;
        transform: translateY(14px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

@keyframes msg-in {
    from {
        opacity: 0;
        transform: translateY(8px) scale(0.96);
    }
    to {
        opacity: 1;
        transform: translateY(0) scale(1);
    }
}

@keyframes msg-in-mine {
    from {
        opacity: 0;
        transform: translateY(8px) translateX(10px) scale(0.96);
    }
    to {
        opacity: 1;
        transform: translateY(0) translateX(0) scale(1);
    }
}

@keyframes reaction-in {
    from {
        opacity: 0;
        transform: translateY(6px) scale(0.4);
    }
    to {
        opacity: 1;
        transform: translateY(0) scale(1);
    }
}

@keyframes reaction-bump {
    0% {
        transform: scale(1);
    }
    45% {
        transform: translateY(-2px) scale(1.22);
    }
    100% {
        transform: scale(1);
    }
}

@keyframes msg-out {
    from {
        opacity: 1;
        transform: translateY(0) scale(1);
    }
    to {
        opacity: 0;
        transform: translateY(-4px) scale(0.95);
    }
}

@keyframes pop-in {
    from {
        opacity: 0;
        transform: translateY(6px) scale(0.96);
    }
    to {
        opacity: 1;
        transform: none;
    }
}

@keyframes dock-pop-in {
    from {
        opacity: 0;
        transform: translateX(-50%) translateY(6px) scale(0.96);
    }
    to {
        opacity: 1;
        transform: translateX(-50%) translateY(0) scale(1);
    }
}

@keyframes modal-in {
    from {
        opacity: 0;
        transform: translateY(10px) scale(0.96);
    }
    to {
        opacity: 1;
        transform: none;
    }
}

@keyframes toast-in {
    from {
        opacity: 0;
        transform: translateY(-8px) scale(0.98);
    }
    to {
        opacity: 1;
        transform: none;
    }
}

@keyframes toast-out {
    to {
        opacity: 0;
        transform: translateY(-6px) scale(0.98);
    }
}

/* Анимации звонка: панель проявляется, карточка и кнопки въезжают, видео-слои
   гаснут из прозрачности. Снятие [hidden] перезапускает их само. */
@keyframes call-in {
    from {
        opacity: 0;
    }
}

@keyframes call-out {
    to {
        opacity: 0;
    }
}

@keyframes call-card-in {
    from {
        opacity: 0;
        transform: translateY(12px) scale(0.95);
    }
}

@keyframes call-btn-in {
    from {
        opacity: 0;
        transform: translateY(8px) scale(0.9);
    }
}

@keyframes call-layer-in {
    from {
        opacity: 0;
    }
}

/* ---- Звонок ---- */
/* Панель — оверлей внутри #room (как .modal), а не часть сетки комнаты: так она
   верна и на десктопе, и в мобильной карусели. z-index выше .thread-bar (100),
   иначе шапка чата проступала бы поверх карточки. */
.call-panel {
    position: absolute;
    inset: 0;
    z-index: 120;
    display: grid;
    place-items: center;
    padding: 20px;
    background: var(--veil);
    animation: call-in 0.2s ease both;
}

/* Уход панели: класс ставит main.js и лишь затем выставляет [hidden] —
   иначе анимация не успевает проиграть и панель пропадала бы рывком. */
.call-panel.closing {
    pointer-events: none;
    animation: call-out 0.18s ease forwards;
}

.call-card {
    position: relative;
    width: min(320px, 100%);
    display: grid;
    justify-items: center;
    gap: 8px;
    padding: 26px 20px 22px;
    border: 1px solid var(--line);
    border-radius: 18px;
    background: var(--material);
    backdrop-filter: var(--glass);
    -webkit-backdrop-filter: var(--glass);
    text-align: center;
    /* Видео-слои лежат абсолютом — карточка режет их по скруглённому краю. */
    overflow: hidden;
    animation: call-card-in 0.26s cubic-bezier(0.32, 1.24, 0.62, 1) both;
}

/* Чужой поток ложится фоном всей карточки, свой — превью в углу. Пока камера
   выключена, оба скрыты атрибутом hidden и карточка показывает .call-avatar. */
.call-layer {
    width: 100%;
    height: 100%;
    object-fit: cover;
    background: #000;
    animation: call-layer-in 0.28s ease both;
}

.call-remote {
    position: absolute;
    inset: 0;
    z-index: 0;
    border-radius: inherit;
}

.call-local {
    position: absolute;
    top: 12px;
    right: 12px;
    z-index: 2;
    width: 84px;
    height: 112px;
    border: 1px solid var(--line);
    border-radius: 12px;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
}

/* Подпись и кнопки — над видеофоном. */
.call-identity {
    position: relative;
    z-index: 1;
    display: grid;
    align-content: center;
    justify-items: center;
    gap: 8px;
}

/* Поверх чужого видео подписи читаются только с тенью. */
.call-card:has(.call-remote:not([hidden])) .call-identity {
    text-shadow: 0 1px 10px rgba(0, 0, 0, 0.8);
}

.call-avatar {
    width: 76px;
    height: 76px;
    font-size: 30px;
}

.call-name {
    margin: 6px 0 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 100%;
    font-size: 18px;
    font-weight: 600;
    letter-spacing: -0.02em;
}

.call-status {
    margin: 0;
    color: var(--muted);
    font-size: 14px;
    font-weight: 500;
    font-variant-numeric: tabular-nums;
}

.call-status.live {
    color: var(--online);
}

.call-actions {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 14px;
    margin-top: 12px;
}

.call-btn {
    width: 52px;
    height: 52px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 50%;
    background: var(--fill-2);
    color: var(--text);
    transition: background 0.18s ease, transform 0.16s ease;
    /* backwards, а не both: с forwards анимация перебивала бы :active. */
    animation: call-btn-in 0.3s cubic-bezier(0.32, 1.24, 0.62, 1) backwards;
}

.call-btn:active {
    transform: scale(0.94);
}

/* Кнопки выезжают по очереди — так панель не появляется одним куском. */
.call-actions .call-btn:nth-child(1) {
    animation-delay: 0.02s;
}

.call-actions .call-btn:nth-child(2) {
    animation-delay: 0.06s;
}

.call-actions .call-btn:nth-child(3) {
    animation-delay: 0.1s;
}

.call-actions .call-btn:nth-child(4) {
    animation-delay: 0.14s;
}

.call-actions .call-btn:nth-child(5) {
    animation-delay: 0.18s;
}

.call-actions .call-btn:nth-child(6) {
    animation-delay: 0.22s;
}

.call-btn-accept {
    background: var(--online);
    color: #04140a;
}

.call-btn-end {
    background: var(--danger);
    color: #fff;
}

.call-btn-mute[aria-pressed="true"] {
    background: var(--fill-3);
    color: var(--muted);
}

/* Перечёркнутый микрофон показывается тем же атрибутом, которым JS отмечает
   mute: отдельный класс не нужен, и состояние нельзя рассинхронизировать. */
.call-btn-mute .call-icon-off {
    display: none;
}

.call-btn-mute[aria-pressed="true"] .call-icon-on {
    display: none;
}

.call-btn-mute[aria-pressed="true"] .call-icon-off {
    display: block;
}

/* У камеры наоборот: выключена по умолчанию, поэтому базовая иконка —
   перечёркнутая, а залитая и акцентная заливка приходят с aria-pressed. */
.call-btn-camera .call-icon-on {
    display: none;
}

.call-btn-camera[aria-pressed="true"] {
    background: var(--accent);
    color: #fff;
}

.call-btn-camera[aria-pressed="true"] .call-icon-on {
    display: block;
}

.call-btn-camera[aria-pressed="true"] .call-icon-off {
    display: none;
}

/* Удалённый поток: элемент не рисуется, но вынесен из сетки комнаты абсолютом
   и не попадает под [hidden] панели, пока звонок идёт. */
#call-audio {
    position: absolute;
    width: 0;
    height: 0;
}

@media (max-width: 860px) {
    :root {
        --sidebar: 220px;
    }

    .lobby {
        padding: max(16px, env(safe-area-inset-top)) max(16px, env(safe-area-inset-right)) max(16px, env(safe-area-inset-bottom)) max(16px, env(safe-area-inset-left));
    }

    .lobby-split {
        grid-template-columns: 1fr;
        gap: 24px;
        justify-items: stretch;
    }

    .media-setup {
        position: static;
        height: auto;
    }

    .preview {
        position: relative;
        top: auto;
        right: auto;
        bottom: auto;
        width: 100%;
        height: auto;
        max-height: none;
        aspect-ratio: 16 / 9;
    }

    .dock-add span {
        display: none;
    }

    .dock-add {
        width: 42px;
        height: 42px;
        padding: 0;
        justify-content: center;
        border-radius: 12px;
    }

    #roster .person,
    #groups .person,
    .chat-switch {
        gap: 8px;
        min-height: 44px;
        padding: 6px 8px;
        font-size: 15px;
    }

    .toasts {
        top: 10px;
        width: min(360px, calc(100% - var(--sidebar) - 16px));
    }
}

@media (max-width: 560px) {
    .room {
        grid-template-columns: 1fr;
    }

    /* Мобильный лейаут: список — во весь экран, чат выезжает справа,
       а список уезжает влево ему навстречу (карусель, см. .sidebar ниже) */
    .sidebar,
    .thread {
        margin: 0;
        border: 0;
        border-radius: 0;
        height: 100%;
    }

    .sidebar {
        position: absolute;
        inset: 0;
        z-index: 3;
        /* Панель прозрачна: список лежит на общем фоне комнаты
           (.room), а не на своей заливке. Тень из базового правила тоже
           убираем: на прозрачной панели во весь экран она висела тёмным
           ореолом. */
        background: none;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        box-shadow: none;
    }

    .thread {
        position: absolute;
        inset: 0;
        z-index: 4;
        /* Фон тот же, что у списка — его рисует комната, панель только
           скользит поверх. */
        background: none;
        transition: translate 0.32s cubic-bezier(0.32, 0.72, 0, 1);
    }

    /* Старт: виден только список, чат скрыт справа */
    .room:not(.direct) .thread {
        translate: 100% 0;
        visibility: hidden;
        transition: translate 0.32s cubic-bezier(0.32, 0.72, 0, 1), visibility 0s 0.32s;
    }

    /* Открылся чат: чат выезжает на экран справа */
    .room.direct .thread {
        translate: 0 0;
        visibility: visible;
    }

    /* Открылся чат: список уезжает влево, чат приезжает справа — одним
       движением-каруселью. Панели прозрачны и делят один фон комнаты
       (.room), поэтому двигаться они должны синхронно: при
       одинаковых дистанции, длительности и кривой правый край списка всё
       время совпадает с левым краем чата, и панели нигде не накладываются.
       visibility гасит список лишь после того, как он уехал, и возвращает
       его сразу на обратном пути — задержка на открытии берётся из
       transition целевого состояния (как у .thread выше). */
    .room .sidebar {
        translate: 0 0;
        visibility: visible;
        transition: translate 0.32s cubic-bezier(0.32, 0.72, 0, 1);
    }

    .room.direct .sidebar {
        translate: -100% 0;
        visibility: hidden;
        transition: translate 0.32s cubic-bezier(0.32, 0.72, 0, 1), visibility 0s 0.32s;
    }

    .dock {
        min-height: 62px;
        gap: 8px;
        padding: 6px;
        border-radius: 12px;
        bottom: 12px;
    }

    .dock-add,
    .dock-btn {
        width: 48px;
        height: 48px;
        flex: 0 0 48px;
        border-radius: 10px;
    }

    .dock-add svg,
    .dock-btn svg {
        width: 28px;
        height: 28px;
    }

    .dock-avatar {
        width: 38px;
        height: 38px;
        font-size: 15px;
    }

    .dock-pop {
        width: min(244px, calc(100% - 24px));
        left: 50%;
        right: auto;
        transform: translateX(-50%);
        animation: dock-pop-in 0.16s ease;
    }

    .toasts {
        left: 50%;
        width: min(360px, calc(100% - 24px));
        transform: translateX(-50%);
    }

    #roster .person,
    #groups .person,
    .chat-switch {
        grid-template-columns: 36px minmax(0, 1fr) auto;
        min-height: 48px;
    }

    /* Мобильный UX: крупные сенсорные элементы */
    .sidebar,
    .thread {
        border-radius: 0;
    }

    /* Контакты и группы: более высокие строки */
    #roster .person,
    #groups .person,
    .chat-switch {
        grid-template-columns: 48px minmax(0, 1fr) auto;
        min-height: 60px;
        gap: 12px;
        margin: 2px 12px;
        padding: 8px 12px;
        border-radius: 10px;
        font-size: 16px;
    }

    .avatar {
        width: 44px;
        height: 44px;
        font-size: 16px;
    }

    .person-status {
        font-size: 14px;
    }

    /* Шапка чата */
    .thread-bar {
        margin: 8px 8px 6px;
        padding: 12px 14px;
        border-radius: 12px;
    }

    .thread-bar .avatar {
        width: 42px;
        height: 42px;
        font-size: 16px;
    }

    .thread-name {
        font-size: 16px;
    }

    .thread-status {
        font-size: 13px;
    }

    .thread-bar > button:first-child {
        width: 48px;
        height: 48px;
    }
}
@media (prefers-reduced-motion: reduce) {
    .lobby-card,
    .msg-row,
    .toast {
        animation: none;
    }

    /* .reaction-chip.fresh/.bump и .msg-row.removing специфичнее одиночного класса,
       поэтому гасим их тем же весом селектора ниже по файлу. */
    .reaction-chip,
    .reaction-chip.fresh,
    .reaction-chip.bump,
    .msg-row.removing {
        animation: none;
    }

    .msg-row.removing {
        transition: none;
    }

    /* Мобильная карусель панелей: список и чат двигаются парой, поэтому
       гасим переходы у обеих сразу — иначе они разъедутся. */
    .room .sidebar,
    .room.direct .sidebar,
    .room:not(.direct) .thread {
        transition: none;
    }

    .join-form > button,
    .ghost,
    .emoji-picker {
        transition: none;
        animation: none;
    }

    /* Подэкраны появляются и исчезают без свайпа; состояние в этом случае переключают
       сами обработчики (openSubSheet/closeSubSheet). */
    .sheet-page.over.push-in,
    .sheet-page.over.push-out,
    .push-aside,
    .push-return {
        animation: none;
    }

    /* .call-panel.closing специфичнее одиночного класса, поэтому гасим тем же
       весом селектора и ниже по файлу, чем объявлены сами анимации звонка. */
    .call-panel,
    .call-panel.closing,
    .call-card,
    .call-layer,
    .call-btn {
        animation: none;
    }
}

/* ===== Мобильный UX: крупные сенсорные элементы (доп. медиа) ===== */
@media (max-width: 560px) {
    .sidebar-scroll {
        padding-bottom: 96px;
    }

    .sidebar-switch {
        margin: 12px 14px 8px;
    }

    .sidebar-switch-item {
        height: 44px;
        font-size: 15px;
    }

    .switch-count {
        min-width: 22px;
        height: 22px;
        font-size: 13px;
    }

    #roster .person,
    #groups .person,
    .chat-switch {
        grid-template-columns: 48px minmax(0, 1fr) auto;
        gap: 12px;
        min-height: 62px;
        margin: 2px 12px;
        padding: 8px 12px;
        border-radius: 10px;
        font-size: 16px;
    }

    .avatar {
        width: 46px;
        height: 46px;
        font-size: 17px;
    }

    .person-status {
        font-size: 14px;
    }

    .thread-bar {
    margin: 0;
    padding: 12px 14px;
    border-radius: 0 0 12px 12px;
    }

    .thread-bar .avatar {
        width: 44px;
        height: 44px;
        font-size: 17px;
    }

    .thread-name {
        font-size: 17px;
    }

    .thread-status {
        font-size: 14px;
    }

    .thread-bar > button:first-child {
        width: 44px;
        height: 44px;
    }

    /* На тач-экране кнопка звонка растёт до высоты шапки (аватар 44px). */
    .thread-call {
        width: 44px;
        height: 44px;
    }

    /* На телефоне звонок занимает весь экран: карточка тянется по панели,
       поэтому её рамка и радиус больше не нужны. */
    .call-panel {
        place-items: stretch;
        padding: 0;
    }

    .call-card {
        width: 100%;
        height: 100%;
        grid-template-rows: 1fr auto;
        padding: max(28px, env(safe-area-inset-top, 0px)) max(20px, env(safe-area-inset-right, 0px)) max(24px, env(safe-area-inset-bottom, 0px)) max(20px, env(safe-area-inset-left, 0px));
        border: 0;
        border-radius: 0;
    }

    .call-local {
        top: max(12px, env(safe-area-inset-top, 0px));
        right: max(12px, env(safe-area-inset-right, 0px));
        width: 104px;
        height: 138px;
    }

    .call-avatar {
        width: 92px;
        height: 92px;
        font-size: 36px;
    }

    .call-btn {
        width: 58px;
        height: 58px;
    }

    .composer {
        margin: 0;
        padding: 8px;
        border-radius: 12px 12px 0 0;
    }

    .chat-form {
        grid-template-columns: 44px minmax(0, 1fr) 44px;
        gap: 6px;
    }

    /* Поле многострочное: одну строку держат 11 + 24 + 11 = 46px, как раньше. */
    .chat-form textarea {
        min-height: 46px;
        max-height: 142px;
        padding: 11px 16px;
        font-size: 16px;
        line-height: 24px;
    }

    .chat-form button[type="submit"],
    .emoji-toggle {
        width: 44px;
        height: 44px;
    }

    .emoji-toggle svg,
    .chat-form button[type="submit"] svg,
    #thread-back svg {
        width: 24px;
        height: 24px;
    }

    .composer > .voice-rec {
        height: 46px;
    }

    .voice-rec-stop {
        width: 44px;
        height: 44px;
    }

    .voice-rec-send {
        width: 44px;
        height: 44px;
    }

    .emoji-picker {
        left: 50%;
        transform: translateX(-50%);
        width: min(374px, 100% - 26px);
        max-width: 356px;
        bottom: calc(var(--composer-space, 65px) + 21px);
        border-radius: 12px;
        animation: emoji-rise-in 0.22s cubic-bezier(0.2, 0.9, 0.3, 1);
    }

    .emoji-grid {
        grid-template-columns: repeat(6, 1fr);
        gap: 4px;
    }

    .emoji-grid button {
        height: 52px;
        font-size: 32px;
        border-radius: 10px;
    }

    .msg-row {
        max-width: min(86%, 42rem);
    }

    .msg-row {
        gap: 6px;
    }

    .msg {
        padding: 8px 13px 8px;
        font-size: 16px;
        line-height: 1.3;
    }

    .msg-row .msg-avatar .avatar {
        width: 27px;
        height: 27px;
        font-size: 11px;
    }

    .msg-meta {
        margin-bottom: 1px;
    }

    .msg b {
        font-size: 13px;
    }

    .msg-time {
        font-size: 10px;
    }

    .msg-row.mine .msg-time {
        font-size: 10px;
    }

    .msg-row.emoji .msg p {
        font-size: 30px;
        line-height: 1.05;
    }

    .msg-media {
        max-width: min(300px, 100%);
        max-height: 240px;
        border-radius: 8px;
        margin-bottom: 3px;
    }

    .msg-audio {
        max-width: 220px;
        height: 32px;
    }

    .voice-player {
        min-width: 180px;
        max-width: 250px;
        gap: 8px;
    }

    .voice-wave {
        height: 30px;
    }

    .voice-play {
        width: 44px;
        height: 44px;
    }

    .msg-reactions {
        gap: 4px;
        margin-top: 4px;
    }
    .reaction-chip {
        height: 26px;
        padding: 0 10px;
        font-size: 13px;
        border-radius: 8px;
    }

    .messages {
        gap: 0;
        padding: 70px 10px var(--composer-space, 65px);
    }

    .day-chip {
        margin: 10px 0 6px;
        padding: 5px 12px;
        font-size: 12px;
    }

    .room-empty p:first-child {
        font-size: 16px;
    }

    .room-empty p:last-child {
        font-size: 14px;
    }

    .msg-edited svg {
        width: 10px;
        height: 10px;
    }

    .toasts {
        top: calc(10px + env(safe-area-inset-top, 0px));
    }

    .dock {
        min-height: 74px;
        gap: 10px;
        padding: 9px;
        border-radius: 12px;
        bottom: calc(14px + env(safe-area-inset-bottom, 0px));
    }

    .dock-add,
    .dock-btn {
        width: 56px;
        height: 56px;
        flex: 0 0 56px;
        border-radius: 10px;
    }

    .dock-add svg,
    .dock-btn svg {
        width: 32px;
        height: 32px;
    }

    .dock-avatar {
        width: 46px;
        height: 46px;
        font-size: 18px;
    }

    .dock-pop {
        bottom: calc(100px + env(safe-area-inset-bottom, 0px));
    }

    .dock-pop-item {
        min-height: 50px;
        font-size: 16px;
    }

    .dock-pop-item svg {
        width: 22px;
        height: 22px;
    }

    .modal {
        padding: 0;
        place-items: stretch;
    }

    .modal-card {
        width: 100%;
        max-width: none;
        height: 100%;
        max-height: none;
        border: 0;
        border-radius: 0;
        background: var(--material);
        backdrop-filter: saturate(180%) blur(40px);
        -webkit-backdrop-filter: saturate(180%) blur(40px);
        box-shadow: none;
    }

    .avatar.sheet-avatar {
        width: 68px;
        height: 68px;
        font-size: 26px;
    }

    /* В листе «Вы» аватар стоит отдельной строкой — он крупнее, чем в шапках карточек,
       и кнопка вокруг него растёт по нему. Карандаш правки — под палец. */
    .avatar.me-avatar {
        width: 128px;
        height: 128px;
        font-size: 48px;
    }

    .me-pencil {
        width: 44px;
        height: 44px;
    }

    .sheet-head {
        grid-template-columns: 68px minmax(0, 1fr);
    }

    .sheet-title {
        font-size: 20px;
    }

    .sheet-sub,
    .sheet-status,
    .sheet-kicker,
    .sheet-warning {
        font-size: 14px;
    }

    .modal-close {
        top: 14px;
        right: 14px;
        width: 44px;
        height: 44px;
        border-radius: 10px;
    }

    .sheet-head {
        padding-right: 48px;
    }

    /* У кнопки «Закрыть» на телефоне те же 44px в углу: место под неё, как у шапки листа. */
    .sheet-nav {
        padding-right: 48px;
    }

    .sheet-form input,
    .me-field input {
        height: 48px;
        font-size: 16px;
    }

    .sheet-form > button,
    .sheet-add > button {
        height: 48px;
        font-size: 16px;
    }

    .sheet-add {
        grid-template-columns: minmax(0, 1fr) 118px;
    }

    .sheet-action,
    .sheet-logout,
    .me-menu-row,
    .theme-option {
        height: 48px;
        font-size: 16px;
    }

    /* Лист листается внутри себя: карточка на телефоне во всю высоту, и прокрутка
       контента не должна уезжать вместе с шапкой листа. Отступы одни на все секции:
       56px сверху — под строку состояния, снизу — под системный жест, по бокам 20px
       (подэкран наследует те же значения, поэтому контент не сдвигается). */
    .sheet {
        display: flex;
        flex-direction: column;
        padding: 56px 20px calc(20px + env(safe-area-inset-bottom, 0px));
    }

    .sheet-back {
        width: 44px;
        height: 44px;
    }

    .sheet-people {
        max-height: none;
        align-self: stretch;
        min-height: 0;
        overflow: auto;
        flex: 1;
    }

    .sheet-people button {
        grid-template-columns: 46px minmax(0, 1fr);
        gap: 12px;
        min-height: 56px;
    }

    .sheet-people .contact-remove {
        width: 44px;
        height: 44px;
    }

    /* Кнопки правки встают под UIN отдельной строкой во всю ширину листа, поэтому на
       телефоне они того же роста, что остальные кнопки листа (.sheet-action). */

    /* Поле даты — та же высота, что у текстовых полей листа на телефоне. */
    .date-value {
        height: 48px;
    }

    /* Селектбокс пола — того же роста, что поля даты и текстовые поля листа, а строки
       списка — под палец. */
    .sheet-select-value {
        height: 48px;
        font-size: 16px;
    }

    .sheet-menu-option {
        height: 44px;
    }

    /* Календарь на телефоне листается пальцем: ячейки крупнее, чем на десктопе. */
    .date-cell,
    .date-nav,
    .date-title {
        height: 40px;
    }

    .ctx-menu {
        min-width: 200px;
        padding: 8px;
        border-radius: 12px;
    }

    .ctx-menu button {
        height: 48px;
        font-size: 16px;
        padding: 0 14px;
    }

    .ctx-menu .ctx-reaction {
        width: 48px;
        height: 48px;
        font-size: 24px;
    }

    .codes-list {
        max-height: 240px;
    }

    /* Лобби: крупные сенсорные элементы */
    .auth-tab {
        height: 44px;
        font-size: 15px;
    }

    .auth-link {
        min-height: 44px;
        padding: 12px 0;
        font-size: 15px;
    }

    .uin-reroll {
        height: 44px;
        padding: 0 16px;
        font-size: 15px;
    }

    .uin-value-btn {
        min-height: 44px;
        padding: 4px 16px;
    }

    .vault-copy {
        width: 44px;
        min-height: 44px;
    }

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

    .uin-search {
        grid-template-columns: minmax(0, 1fr) 44px;
        padding: 4px 4px 4px 12px;
    }

    .uin-search input {
        height: 44px;
        font-size: 15px;
    }

    .uin-search button {
        width: 44px;
        height: 44px;
    }

    .ghost {
        height: 44px;
        padding: 0 14px;
        font-size: 14px;
    }

    .toast {
        min-height: 44px;
        padding: 12px 14px;
        border-radius: 12px;
        font-size: 14px;
    }
}

/* Мобильное правило .emoji-picker (max-width: 560px) объявлено ниже общего
   блока prefers-reduced-motion и перебивает его тем же весом селектора,
   поэтому гасим анимацию пикера повторно — уже в самом конце файла. */
@media (prefers-reduced-motion: reduce) {
    .emoji-picker {
        animation: none;
    }
}
