Files
vidconf/frontend/src/styles/room.css
Max Ronzhin b11feac529
Some checks failed
CI / backend (push) Has been cancelled
CI / frontend (push) Has been cancelled
feat(room): компактная шторка настроек устройств на мобильном
На ширине ≤600px (брейкпоинт комнаты) DeviceSettingsDialog рендерит те же
поля не модалкой по центру, а шторкой снизу (`room-sheet-overlay`/
`room-sheet-panel`) — по образцу решения оператора: так быстрее и привычнее
с телефона. Десктоп не меняется.

Закрытие — клик вне панели (было и раньше), Escape и возврат фокуса через
существующий useModalDismiss, и свайп вниз за ручку (JS считает смещение
пальца и либо закрывает панель, либо возвращает её на место пружиной).
2026-07-27 17:32:42 +03:00

605 lines
27 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* VidConf — стили экрана комнаты конференции (тёмная тема, [data-theme="room"]).
* Приближение к design/mockups/room.html.
*
* ВАЖНО (см. также комментарий в RoomPage.tsx): видео-сетка участников
* рендерится компонентами @livekit/components-react (GridLayout/FocusLayout/
* CarouselLayout/ParticipantTile) — их внутренняя разметка не совпадает 1:1 с
* `.tile`/`.tile-preview` из макета. Здесь мы (1) переопределяем CSS-переменные
* темы LiveKit (`--lk-*`) значениями токенов VidConf, чтобы цвета/радиусы
* совпадали с дизайн-системой, и (2) стилизуем только внешние контейнеры,
* которые нам доступны через className. Точная пиксельная подгонка внутренней
* разметку ParticipantTile (позиция имени/иконки микрофона 1:1 как в
* макете) вынесена в отдельную доводку.
*/
[data-theme='room'] { height: 100%; }
.room-shell { display: flex; flex-direction: column; height: 100vh; background: var(--color-room-bg); }
.room-topbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-3) var(--space-6);
background: var(--color-room-surface);
border-bottom: 1px solid var(--color-room-tile-border);
flex-shrink: 0;
}
.room-title-block h1 { font: var(--text-h3); margin: 0 0 2px; color: var(--color-room-text-primary); }
.room-title-block p { font: var(--text-body); margin: 0; color: var(--color-room-text-secondary); display: flex; align-items: center; gap: 8px; }
.rec-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--color-room-mic-on); box-shadow: 0 0 0 3px rgba(70, 217, 122, 0.25); }
/* Инвайт-чип (номер конференции + копирование ссылки) — см. RoomTopbar.tsx. */
.room-invite-chip {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
border-radius: var(--radius-full);
border: 1px solid var(--color-room-tile-border);
background: var(--color-room-tile);
color: var(--color-room-text-primary);
font: var(--text-body);
font-weight: 600;
}
.room-invite-chip:hover { background: var(--color-room-tile-hover); }
.room-invite-chip span { font: var(--text-mono-sm); color: var(--color-room-text-secondary); }
.room-main { display: flex; flex: 1; min-height: 0; }
.stage { flex: 1; display: flex; flex-direction: column; padding: var(--space-5) var(--space-6); gap: var(--space-4); min-width: 0; }
/*
* Демонстрация экрана: `object-fit: cover` (дефолт VideoTrack для
* камеры) обрезает края — для текста/таблиц на экране это критично, поэтому
* для screenshare-видео явно переключаем на `contain` (letterbox вместо
* обрезки); фон — чёрный, как у самого видео-элемента библиотеки.
* `data-lk-source` — штатный атрибут @livekit/components-react на <video>.
*/
video[data-lk-source='screen_share'] { object-fit: contain; background: #000; }
/*
* `.stage-tiles` — общий className и для FocusLayoutContainer (несколько
* участников), и для GridLayout (один/два участника, см. RoomStage.tsx) —
* тут только то, что нужно ОБОИМ (заполнить оставшуюся высоту `.stage`).
* Раскладку колонок (`grid-template-columns`) переопределяем НИЖЕ отдельно,
* только для варианта с FocusLayoutContainer (`.lk-focus-layout`) — раньше
* это правило висело прямо на `.stage-tiles` и наружу задевало GridLayout:
* при одном участнике его плитка сжималась в узкую 22%-колонку слева, а
* остальная сцена оставалась пустой (баг, поймали на скриншоте).
*/
.stage-tiles {
flex: 1;
min-height: 0;
}
/*
* FocusLayoutContainer рендерит `.lk-focus-layout` (grid, первая колонка —
* узкая, под превью; вторая — широкая, под активного спикера) — сужаем
* первую колонку под наш вкус (дефолтный `1fr` слишком гуляет на широких
* мониторах). Селектор нарочно составной (`.stage-tiles.lk-focus-layout`),
* чтобы НЕ задеть `.lk-grid-layout` (тот же className `.stage-tiles` также
* висит на GridLayout при 12 участниках, см. выше). `!important` не нужен:
* `room.css` импортируется в RoomPage.tsx ПОСЛЕ `@livekit/components-styles`,
* поэтому при равной специфичности наши значения и так побеждают по порядку
* в исходном CSS.
*/
.stage-tiles.lk-focus-layout {
grid-template-columns: minmax(200px, 22%) 1fr;
}
/*
* Колонка миниатюр остальных участников — CarouselLayout без явного
* `orientation` (см. комментарий в RoomStage.tsx): при узкой и высокой форме
* контейнера (десктоп, боковая колонка) сам определяет `vertical` и берёт
* встроенные стили `.lk-carousel[data-lk-orientation="vertical"]` из
* @livekit/components-styles (flex-direction: column, скролл по вертикали,
* тайлы делятся по высоте на --lk-max-visible-tiles). Здесь только
* тонкая настройка скроллбара под тёмную тему комнаты — сами
* flex-direction/overflow не трогаем, чтобы не сломать переключение
* ориентации на мобильной ширине (см. media-запрос ниже).
*/
.lk-carousel::-webkit-scrollbar { width: 6px; height: 6px; }
.lk-carousel::-webkit-scrollbar-thumb { background: var(--color-room-tile-border); border-radius: 3px; }
/*
* Мобильная ширина: @livekit/components-styles сам переключает
* `.lk-focus-layout` на `grid-template-rows: 5fr 1fr` (спикер сверху,
* колонка миниатюр становится узкой горизонтальной полосой снизу,
* `.lk-carousel{order:1}`) — колонка шириной 22% там физически не
* поместится, поэтому здесь возвращаем `grid-template-columns` обратно в
* `1fr` (наше правило выше иначе продолжало бы действовать поверх их
* media-запроса — у обоих селекторов равная специфичность, и то, что
* стоит позже в исходнике, побеждает, а мы просто идём в файле позже).
*/
@media (max-width: 600px) {
.stage-tiles.lk-focus-layout { grid-template-columns: 1fr; }
.stage { padding: var(--space-3) var(--space-4); gap: var(--space-3); }
}
/*
* `.room-single-tile` — сцена мини-плеера (Document PiP, `RoomStage` с
* `variant="pip"`): единственная плитка активного окна на весь контейнер, без
* карусели/грида. Портал рендерит
* `RoomStage` НАПРЯМУЮ в `pipWindow.document.body` — своей `.room-shell`/
* `.room-main` цепочки высот там нет, поэтому `.lk-participant-tile`
* растягиваем явным `flex: 1` (а не полагаемся на %-высоту от родителя, как
* `.stage-tiles` в основном окне) плюс подстраховочный `min-height`, чтобы
* плитка не схлопывалась, если видео ещё не выдало реальные размеры.
*/
.room-single-tile { flex: 1; min-height: 0; display: flex; }
.room-single-tile .lk-participant-tile { flex: 1; min-height: 220px; width: 100%; }
/* Нижний тулбар: свои кнопки на хуках LiveKit (TrackToggle/DisconnectButton) */
.room-toolbar {
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
background: var(--color-room-surface);
border-top: 1px solid var(--color-room-tile-border);
padding: var(--space-3) var(--space-6);
flex-shrink: 0;
}
.tb-btn {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
background: transparent;
border: none;
padding: 8px 18px;
border-radius: var(--radius-md);
color: var(--color-room-text-primary);
min-width: 76px;
cursor: pointer;
}
.tb-btn .icon-shell {
width: 48px;
height: 48px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 1.5px solid var(--color-room-tile-border);
background: var(--color-room-tile);
color: var(--color-room-text-primary);
}
.tb-btn span.label { font: var(--text-caption); text-transform: none; letter-spacing: normal; color: var(--color-room-text-secondary); font-weight: 500; }
.tb-btn:hover .icon-shell { background: var(--color-room-tile-hover); }
.tb-btn.is-off .icon-shell { background: var(--color-room-mic-off); border-color: var(--color-room-mic-off); color: #3a0f16; }
.tb-btn.is-off span.label { color: var(--color-room-mic-off); font-weight: 700; }
/* Кнопка «Демонстрация» — активное состояние подсвечено зелёным,
как и другие «позитивные» индикаторы комнаты (ср. .rec-dot/--color-room-mic-on). */
.tb-btn.is-sharing .icon-shell { background: var(--color-room-mic-on); border-color: var(--color-room-mic-on); color: #10331f; }
.tb-btn.is-sharing span.label { color: var(--color-room-mic-on); font-weight: 700; }
.tb-btn:disabled { opacity: 0.6; cursor: default; }
.tb-btn.danger .icon-shell { border-color: var(--color-room-danger); color: var(--color-room-danger); background: transparent; }
.tb-btn.danger:hover .icon-shell { background: var(--color-room-danger-bg); color: #fff; border-color: var(--color-room-danger-bg); }
.tb-btn.danger span.label { color: var(--color-room-danger); font-weight: 700; }
/* Кнопка «Чат» — подсветка при открытой панели + бейдж непрочитанных. */
.tb-btn .icon-shell { position: relative; }
.tb-btn.is-panel-open .icon-shell { background: var(--color-room-surface-raised); border-color: var(--color-room-speaker-ring); color: var(--color-room-speaker-ring); }
.badge-count {
position: absolute;
top: -2px;
right: -2px;
background: var(--color-room-mic-on);
color: #0e3320;
font-size: 10px;
font-weight: 700;
width: 16px;
height: 16px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
/* ---------- Панель чата (см. design/mockups/room.html, .chat-panel) ---------- */
.chat-panel {
width: 320px;
flex-shrink: 0;
background: var(--color-room-surface-raised);
border-left: 1px solid var(--color-room-tile-border);
display: flex;
flex-direction: column;
box-shadow: var(--shadow-room-panel);
}
.chat-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-4) var(--space-5);
border-bottom: 1px solid var(--color-room-tile-border);
flex-shrink: 0;
}
.chat-head h2 { font: var(--text-h3); margin: 0; color: var(--color-room-text-primary); }
.chat-head button { background: none; border: none; color: var(--color-room-text-secondary); cursor: pointer; display: flex; }
.chat-head button:hover { color: var(--color-room-text-primary); }
.chat-status-banner {
display: flex;
align-items: center;
gap: 8px;
margin: 0;
padding: var(--space-3) var(--space-5);
background: var(--color-room-danger-bg);
color: #fff;
font: var(--text-body);
flex-shrink: 0;
}
.chat-empty { font: var(--text-body); color: var(--color-room-text-tertiary); margin: auto; text-align: center; }
.chat-messages {
flex: 1;
overflow-y: auto;
padding: var(--space-5);
display: flex;
flex-direction: column;
gap: var(--space-4);
}
.chat-messages::-webkit-scrollbar { width: 6px; }
.chat-messages::-webkit-scrollbar-thumb { background: var(--color-room-tile-border); border-radius: 3px; }
.msg .msg-meta { display: flex; align-items: baseline; gap: 8px; margin-bottom: 4px; }
.msg .msg-author { font: var(--text-body); font-weight: 700; color: var(--color-room-mic-on); }
.msg .msg-guest-badge {
font: var(--text-caption);
color: var(--color-room-text-tertiary);
border: 1px solid var(--color-room-tile-border);
border-radius: var(--radius-sm);
padding: 0 6px;
text-transform: none;
letter-spacing: normal;
}
.msg .msg-time { font: 12px var(--font-body); color: var(--color-room-text-tertiary); }
.msg .msg-bubble {
background: var(--color-room-tile);
border-radius: var(--radius-md);
padding: 10px 14px;
font: var(--text-body);
color: var(--color-room-text-primary);
white-space: pre-wrap;
word-break: break-word;
}
.chat-input-row {
padding: var(--space-4);
border-top: 1px solid var(--color-room-tile-border);
display: flex;
align-items: flex-end;
gap: 8px;
flex-shrink: 0;
}
.chat-input-row textarea {
flex: 1;
resize: none;
background: var(--color-room-tile);
border: 1px solid var(--color-room-tile-border);
border-radius: var(--radius-lg);
padding: 10px 16px;
color: var(--color-room-text-primary);
font: var(--text-body-lg);
max-height: 96px;
}
.chat-input-row textarea::placeholder { color: var(--color-room-text-tertiary); }
.chat-input-row textarea:disabled { opacity: 0.6; }
.chat-input-row button {
width: 42px;
height: 42px;
border-radius: 50%;
background: var(--color-room-mic-on);
border: none;
color: #10331f;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
cursor: pointer;
}
.chat-input-row button:disabled { opacity: 0.5; cursor: default; }
@media (max-width: 900px) {
.chat-panel {
position: fixed;
inset: 0;
top: 64px;
bottom: 96px;
width: auto;
z-index: 20;
}
}
/*
* ---------- Диалог «Настройки устройств» ----------
* Нет отдельного макета для диалога в design/mockups/room.html — собран из
* уже утверждённых токенов тёмной темы комнаты (`--color-room-*`), тот же
* структурный паттерн (overlay/panel/head/close/field), что и модалки
* оболочки (`shell.css` .modal-*), но перекрашенный под комнату: `.modal-*`
* оболочки используют `--color-ink-*`, которые не переопределяются внутри
* `[data-theme="room"]`, — переиспользовать их напрямую нельзя, получилась
* бы светлая панель на тёмном фоне комнаты.
*/
.room-modal-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.55);
display: flex;
align-items: center;
justify-content: center;
padding: var(--space-6);
z-index: 200;
}
.room-modal-panel {
background: var(--color-room-surface-raised);
border: 1px solid var(--color-room-tile-border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-room-panel);
padding: var(--space-6);
width: 100%;
max-width: 420px;
max-height: calc(100vh - var(--space-12));
overflow-y: auto;
}
.room-modal-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); margin-bottom: var(--space-5); }
.room-modal-head h2 { font: var(--text-h3); color: var(--color-room-text-primary); margin: 0; }
.room-modal-close {
width: 32px;
height: 32px;
border-radius: 50%;
border: 1px solid var(--color-room-tile-border);
background: transparent;
color: var(--color-room-text-secondary);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
cursor: pointer;
}
.room-modal-close:hover { color: var(--color-room-text-primary); border-color: var(--color-room-text-secondary); }
.room-field { margin-bottom: var(--space-5); }
.room-field:last-child { margin-bottom: 0; }
.room-field label { display: block; font: var(--text-body); font-weight: 600; color: var(--color-room-text-primary); margin-bottom: var(--space-2); }
.room-field select {
width: 100%;
font: var(--text-body-lg);
color: var(--color-room-text-primary);
padding: 10px 14px;
border-radius: var(--radius-md);
border: 1px solid var(--color-room-tile-border);
background: var(--color-room-tile);
font-family: var(--font-body);
}
.room-field select:focus { outline: none; border-color: var(--color-room-focus-ring); }
.room-field-label { display: block; font: var(--text-body); font-weight: 600; color: var(--color-room-text-primary); margin-bottom: var(--space-2); }
.room-field-hint { font: var(--text-body); color: var(--color-room-text-secondary); margin: 0; }
/*
* ---------- Шторка «Настройки устройств» на мобильном ----------
* Тот же диалог (`DeviceSettingsDialog`), что модалка выше, — только контейнер
* снизу вместо центра экрана: `.room-sheet-overlay`/`.room-sheet-panel` вместо
* `.room-modal-overlay`/`.room-modal-panel`, разметка полей (`.room-field` и
* ниже) общая. Ручка `.room-sheet-handle` — свайп вниз для закрытия (JS считает
* смещение пальца и сам решает, закрывать или вернуть панель на место;
* `transition` тут — только пружина возврата, во время самого драга
* компонент подставляет инлайновый `transition: none`).
*/
.room-sheet-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.55);
display: flex;
align-items: flex-end;
justify-content: center;
z-index: 200;
}
.room-sheet-panel {
background: var(--color-room-surface-raised);
border: 1px solid var(--color-room-tile-border);
border-bottom: none;
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
box-shadow: var(--shadow-room-panel);
padding: var(--space-4) var(--space-6) calc(var(--space-6) + env(safe-area-inset-bottom));
width: 100%;
max-height: 80vh;
overflow-y: auto;
transition: transform 160ms ease-out;
}
.room-sheet-handle {
width: 40px;
height: 4px;
border-radius: 2px;
background: var(--color-room-tile-border);
margin: 0 auto var(--space-4);
touch-action: none;
}
/*
* ---------- Чип «Вы демонстрируете экран» ----------
* Рендерится внутри `RoomParticipantTile` (см. её `TileBody`) — т.е. внутри
* `.lk-participant-tile`, который у @livekit/components-react уже
* `position: relative` (см. её базовые стили), поэтому оверлей позиционируем
* абсолютно прямо относительно плитки, без правок родительских контейнеров.
*/
.stage-sharing-chip {
position: absolute;
top: 12px;
left: 12px;
z-index: 5;
display: flex;
align-items: center;
gap: 10px;
padding: 8px 14px;
border-radius: var(--radius-full);
background: rgba(30, 30, 30, 0.85);
border: 1px solid var(--color-room-mic-on);
color: var(--color-room-text-primary);
font: var(--text-body);
box-shadow: var(--shadow-md);
}
.stage-sharing-chip svg { width: 18px; height: 18px; color: var(--color-room-mic-on); flex-shrink: 0; }
.stage-sharing-chip button {
border: none;
border-radius: var(--radius-full);
background: var(--color-room-mic-on);
color: #10331f;
font: var(--text-body);
font-weight: 700;
padding: 4px 12px;
cursor: pointer;
flex-shrink: 0;
}
.stage-sharing-chip button:hover { filter: brightness(0.95); }
/*
* ---------- Кнопка закрепления участника (булавка) ----------
* Рендерится внутри `RoomParticipantTile` (см. её `TileBody`), т.е. внутри
* `.lk-participant-tile` — тот у @livekit/components-styles уже
* `position: relative`. Геометрия и логика появления повторяют штатную
* `.lk-focus-toggle-button` той же библиотеки (правый верхний угол, видна по
* наведению) — чтобы плитка выглядела цельно, а не «нашей кнопкой поверх
* чужой». Отличие: у закреплённой плитки булавка видна ВСЕГДА и подсвечена —
* закрепление должно читаться без наведения.
*/
.room-pin-toggle {
position: absolute;
top: 0.25rem;
right: 0.25rem;
z-index: 5;
display: flex;
padding: 0.25rem;
border: none;
border-radius: calc(var(--lk-border-radius, 0.5rem) / 2);
background: rgba(0, 0, 0, 0.5);
color: var(--color-room-text-primary);
cursor: pointer;
opacity: 0;
transition: opacity 0.2s ease-in-out;
transition-delay: 0.2s;
}
.room-pin-toggle svg { width: 20px; height: 20px; }
.lk-participant-tile:hover .room-pin-toggle,
.lk-participant-tile:focus-within .room-pin-toggle,
.room-pin-toggle.is-pinned { opacity: 1; transition-delay: 0s; }
.room-pin-toggle.is-pinned { background: var(--color-room-mic-on); color: #10331f; }
/* Тач-устройства (мобильный/планшет): hover нет вовсе — иначе булавка была бы
недоступна. Тулбар комнаты не задет: кнопка живёт на плитке. */
@media (hover: none) {
.room-pin-toggle { opacity: 1; transition-delay: 0s; }
}
/* Метка закреплённой плитки — рамка по контуру. Класс закрепления висит на
кнопке (корневой div плитки рендерит библиотека, свой класс туда не
добавить), поэтому селектор идёт через `:has()`. */
.lk-participant-tile:has(.room-pin-toggle.is-pinned) {
outline: 2px solid var(--color-room-mic-on);
outline-offset: -2px;
}
/* ---------- Заглушка «конференция в мини-окне» (Document PiP) ---------- */
.room-pip-placeholder {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--space-4);
color: var(--color-room-text-secondary);
font: var(--text-body-lg);
text-align: center;
}
.room-pip-placeholder svg { width: 48px; height: 48px; color: var(--color-room-text-tertiary); }
/* ---------- Аватар вместо иконки-заглушки при выключенной камере ---------- */
.room-tile-avatar {
width: 40%;
height: 40%;
min-width: 32px;
min-height: 32px;
max-width: 96px;
max-height: 96px;
font-size: clamp(12px, 3vw, 28px);
font-weight: 700;
}
.room-loader, .room-error {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--space-3);
height: 100vh;
color: var(--color-room-text-secondary);
font: var(--text-body-lg);
background: var(--color-room-bg);
}
.room-error .btn-primary { margin-top: var(--space-2); }
/* ---------- Переопределение темы @livekit/components-react токенами VidConf ---------- */
[data-lk-theme='default'] {
--lk-bg: var(--color-room-bg);
--lk-bg2: var(--color-room-surface);
--lk-bg3: var(--color-room-tile);
--lk-bg4: var(--color-room-tile-hover);
--lk-bg5: var(--color-room-tile-hover);
--lk-fg: var(--color-room-text-primary);
--lk-fg2: var(--color-room-text-primary);
--lk-fg3: var(--color-room-text-secondary);
--lk-fg4: var(--color-room-text-secondary);
--lk-border-color: var(--color-room-tile-border);
--lk-accent-fg: #10331f;
--lk-accent-bg: var(--color-room-speaker-ring);
--lk-danger-fg: #fff;
--lk-danger: var(--color-room-danger-bg);
--lk-success: var(--color-room-mic-on);
--lk-control-fg: var(--color-room-text-primary);
--lk-control-bg: var(--color-room-tile);
--lk-control-hover-bg: var(--color-room-tile-hover);
--lk-font-family: var(--font-body);
--lk-border-radius: var(--radius-lg);
--lk-grid-gap: var(--space-4);
--lk-control-bar-height: 0px;
}
/* ---------- Узкие экраны: топбар и тулбар комнаты ----------
ВАЖНО: этот блок обязан идти строго ПОСЛЕДНИМ в файле. Селекторы ниже
(`.tb-btn`, `.room-toolbar`) имеют ту же специфичность, что и безусловные
правила выше, а при равной специфичности побеждает порядок в исходнике, а
не факт совпадения media query — тот же грабли уже ловили в
auth.css/calendar.css/lobby.css (см. комментарии там). Первая версия этого
блока стояла в середине файла: `.room-invite-chip` (объявлен ВЫШЕ) скрылся,
а `.tb-btn`/`.room-toolbar` (объявлены НИЖЕ) продолжали побеждать — тулбар
на мобильном не менялся вовсе.
Топбар: скрываем название конференции и строку статуса — они и так есть
в самой конференции, а вот чип «№ … / Пригласить» с телефона нужен чаще
(скопировать ссылку и позвать участника), поэтому он остаётся.
Тулбар: демонстрация экрана, полноэкранный режим и мини-окно с телефона
практически не нужны, а все 8 кнопок в ширину экрана физически не
помещаются — скрываем их и уплотняем оставшиеся пять
(микрофон/камера/устройства/чат/выход). `flex-wrap` + `min-width: 0` —
страховка на совсем узких экранах, чтобы футер ни при каких подписях не
вылезал за ширину окна. */
@media (max-width: 600px) {
.room-topbar { padding: var(--space-3) var(--space-4); justify-content: flex-end; }
.room-title-block { display: none; }
.tb-btn--screenshare,
.tb-btn--fullscreen,
.tb-btn--pip { display: none; }
.room-toolbar { padding: var(--space-3) var(--space-2); gap: 2px; flex-wrap: wrap; }
.tb-btn { min-width: 0; padding: 6px 6px; }
.tb-btn .icon-shell { width: 40px; height: 40px; }
.tb-btn span.label { font-size: 11px; }
}