Compare commits
7 Commits
v0.0.2
...
dbda749756
| Author | SHA1 | Date | |
|---|---|---|---|
| dbda749756 | |||
| 3c36a45d57 | |||
| a04ae3152f | |||
| 6813464e92 | |||
| ac9832c5a1 | |||
| 84e2ab8756 | |||
| 372a7a9378 |
@@ -98,7 +98,7 @@ SMTP_TIMEOUT_S=30
|
|||||||
# --- Версия инстанса (релиз v0.0.1) ---
|
# --- Версия инстанса (релиз v0.0.1) ---
|
||||||
# install.sh копирует значение из корневого файла VERSION при каждой
|
# install.sh копирует значение из корневого файла VERSION при каждой
|
||||||
# установке/обновлении — руками менять не нужно.
|
# установке/обновлении — руками менять не нужно.
|
||||||
VIDCONF_VERSION=0.0.2
|
VIDCONF_VERSION=0.0.3
|
||||||
|
|
||||||
# --- Профили compose. Дефолт ниже (`media,monitoring`) — только для ручного
|
# --- Профили compose. Дефолт ниже (`media,monitoring`) — только для ручного
|
||||||
# `docker compose up` БЕЗ install.sh: медиа (LiveKit+coturn) + мониторинг,
|
# `docker compose up` БЕЗ install.sh: медиа (LiveKit+coturn) + мониторинг,
|
||||||
|
|||||||
28
CHANGELOG.md
28
CHANGELOG.md
@@ -3,6 +3,33 @@
|
|||||||
Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.1.0/),
|
Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.1.0/),
|
||||||
проект придерживается [семантического версионирования](https://semver.org/lang/ru/).
|
проект придерживается [семантического версионирования](https://semver.org/lang/ru/).
|
||||||
|
|
||||||
|
## [0.0.3] — 2026-07-26
|
||||||
|
|
||||||
|
Мобильная версия: адаптивная вёрстка для узких экранов на ключевых экранах
|
||||||
|
сервиса, найденная и поправленная по итогам ручного прохода по мобильному
|
||||||
|
интерфейсу.
|
||||||
|
|
||||||
|
### Исправления
|
||||||
|
- Вход/регистрация (`AuthLayout`): заголовок «Ваша инфраструктура» вылезал за
|
||||||
|
край брендовой панели на узких экранах — кегль теперь адаптивно уменьшается
|
||||||
|
(`clamp()`) вместо обрезки.
|
||||||
|
- Комната конференции: топбар и тулбар не помещались на мобильной ширине
|
||||||
|
(тулбар с 8 кнопками физически шире экрана, крайние кнопки были обрезаны без
|
||||||
|
возможности до них докрутить). На мобильном тулбар сокращён до
|
||||||
|
микрофона/камеры/устройств/чата/выхода (демонстрация экрана,
|
||||||
|
полноэкранный режим и мини-окно скрыты — редко нужны с телефона), в топбаре
|
||||||
|
скрыт инвайт-чип, остаются только название конференции и статус.
|
||||||
|
- Админка: заголовок и 4 вкладки раздела не помещались в ширину (вкладки
|
||||||
|
переносятся в 2 ряда, поясняющий текст под заголовком скрыт на мобильном);
|
||||||
|
таблицы «Конференции» и «Пользователи» обрезались вбок — на мобильном в них
|
||||||
|
остаются только ключевые колонки, название конференции стало кнопкой,
|
||||||
|
открывающей то же окно редактирования, что и раньше (туда же добавлена
|
||||||
|
кнопка удаления — на мобильном столбец действий скрыт).
|
||||||
|
- Подключение к конференции (`/join`): кнопка «Ввести другую ссылку или
|
||||||
|
номер» на экране завершённой конференции вылезала за карточку на узких
|
||||||
|
экранах (единственная кнопка на этом флоу без формы-обёртки, задающей ей
|
||||||
|
ширину, а `.btn` держит `white-space: nowrap`).
|
||||||
|
|
||||||
## [0.0.2] — 2026-07-26
|
## [0.0.2] — 2026-07-26
|
||||||
|
|
||||||
Релиз воспроизводимого боевого развёртывания: конфигурация деплоя перенесена в
|
Релиз воспроизводимого боевого развёртывания: конфигурация деплоя перенесена в
|
||||||
@@ -59,5 +86,6 @@
|
|||||||
|
|
||||||
Первоначальная версия VidConf.
|
Первоначальная версия VidConf.
|
||||||
|
|
||||||
|
[0.0.3]: https://github.com/MaxRonzhin/vidconf_rel/releases/tag/v0.0.3
|
||||||
[0.0.2]: https://github.com/MaxRonzhin/vidconf_rel/releases/tag/v0.0.2
|
[0.0.2]: https://github.com/MaxRonzhin/vidconf_rel/releases/tag/v0.0.2
|
||||||
[0.0.1]: https://github.com/MaxRonzhin/vidconf_rel/releases/tag/v0.0.1
|
[0.0.1]: https://github.com/MaxRonzhin/vidconf_rel/releases/tag/v0.0.1
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ services:
|
|||||||
MEDIA_ROOT: ${MEDIA_ROOT:-/app/media}
|
MEDIA_ROOT: ${MEDIA_ROOT:-/app/media}
|
||||||
# Версия инстанса (релиз v0.0.1) — install.sh копирует значение
|
# Версия инстанса (релиз v0.0.1) — install.sh копирует значение
|
||||||
# из файла VERSION (корень репозитория) в .env; отдаётся в GET /api/health.
|
# из файла VERSION (корень репозитория) в .env; отдаётся в GET /api/health.
|
||||||
VIDCONF_VERSION: ${VIDCONF_VERSION:-0.0.2}
|
VIDCONF_VERSION: ${VIDCONF_VERSION:-0.0.3}
|
||||||
# config/ лежит в корне репозитория и не попадает в образ (контекст сборки —
|
# config/ лежит в корне репозитория и не попадает в образ (контекст сборки —
|
||||||
# только backend/), поэтому plugins.yaml монтируется отдельно.
|
# только backend/), поэтому plugins.yaml монтируется отдельно.
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -50,7 +50,18 @@ function StatusBadge({ conference }: { conference: AdminConferenceOut }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Форма быстрого редактирования конференции из админки (title/время/пароль/рассылка). */
|
/** Форма быстрого редактирования конференции из админки (title/время/пароль/рассылка). */
|
||||||
function EditConferenceModal({ conference, onClose }: { conference: AdminConferenceOut; onClose: () => void }) {
|
function EditConferenceModal({
|
||||||
|
conference,
|
||||||
|
onClose,
|
||||||
|
onDelete,
|
||||||
|
deleting,
|
||||||
|
}: {
|
||||||
|
conference: AdminConferenceOut
|
||||||
|
onClose: () => void
|
||||||
|
/** Удаление конференции — та же мутация, что и у корзины в строке таблицы (см. AdminConferencesTab). */
|
||||||
|
onDelete: () => void
|
||||||
|
deleting: boolean
|
||||||
|
}) {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const initialSlot = conference.scheduled_at ? toLocalDateTimeParts(conference.scheduled_at) : { date: '', time: '' }
|
const initialSlot = conference.scheduled_at ? toLocalDateTimeParts(conference.scheduled_at) : { date: '', time: '' }
|
||||||
@@ -64,6 +75,7 @@ function EditConferenceModal({ conference, onClose }: { conference: AdminConfere
|
|||||||
const [summaryRecipients, setSummaryRecipients] = useState<'' | SummaryRecipientsMode>(
|
const [summaryRecipients, setSummaryRecipients] = useState<'' | SummaryRecipientsMode>(
|
||||||
conference.summary_recipients ?? '',
|
conference.summary_recipients ?? '',
|
||||||
)
|
)
|
||||||
|
const [confirmDelete, setConfirmDelete] = useState(false)
|
||||||
|
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationFn: (payload: AdminConferenceUpdatePayload) => updateAdminConference(conference.id, payload),
|
mutationFn: (payload: AdminConferenceUpdatePayload) => updateAdminConference(conference.id, payload),
|
||||||
@@ -179,13 +191,31 @@ function EditConferenceModal({ conference, onClose }: { conference: AdminConfere
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="modal-actions">
|
<div className="modal-actions modal-actions--split">
|
||||||
<button type="button" className="btn btn-secondary" onClick={onClose}>
|
{confirmDelete ? (
|
||||||
Отмена
|
<div className="row-actions">
|
||||||
</button>
|
<span className="field-hint">Удалить без возможности восстановления?</span>
|
||||||
<button type="submit" className="btn btn-primary" disabled={mutation.isPending}>
|
<button type="button" className="btn btn-secondary" onClick={() => setConfirmDelete(false)}>
|
||||||
{mutation.isPending ? 'Сохраняем…' : 'Сохранить'}
|
Нет
|
||||||
</button>
|
</button>
|
||||||
|
<button type="button" className="btn btn-danger" disabled={deleting} onClick={onDelete}>
|
||||||
|
{deleting ? 'Удаляем…' : 'Да, удалить'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<button type="button" className="btn btn-danger" onClick={() => setConfirmDelete(true)}>
|
||||||
|
<Trash2 style={{ width: 16, height: 16 }} aria-hidden="true" />
|
||||||
|
Удалить
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<div className="modal-actions-group">
|
||||||
|
<button type="button" className="btn btn-secondary" onClick={onClose}>
|
||||||
|
Отмена
|
||||||
|
</button>
|
||||||
|
<button type="submit" className="btn btn-primary" disabled={mutation.isPending}>
|
||||||
|
{mutation.isPending ? 'Сохраняем…' : 'Сохранить'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -293,6 +323,9 @@ export function AdminConferencesTab() {
|
|||||||
queryClient.invalidateQueries({ queryKey: ['admin', 'conferences'] })
|
queryClient.invalidateQueries({ queryKey: ['admin', 'conferences'] })
|
||||||
toast.show('Конференция удалена', 'success')
|
toast.show('Конференция удалена', 'success')
|
||||||
setConfirmingDeleteId(null)
|
setConfirmingDeleteId(null)
|
||||||
|
// Удаление могло прийти и из модалки редактирования (кнопка «Удалить»
|
||||||
|
// там дёргает эту же мутацию) — закрываем её тоже, если открыта.
|
||||||
|
setEditingConference(null)
|
||||||
},
|
},
|
||||||
onError: (err: unknown) => {
|
onError: (err: unknown) => {
|
||||||
if (err instanceof ApiError && err.status === 409) {
|
if (err instanceof ApiError && err.status === 409) {
|
||||||
@@ -340,7 +373,7 @@ export function AdminConferencesTab() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="table-card">
|
<div className="table-card conferences-table">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -363,11 +396,16 @@ export function AdminConferencesTab() {
|
|||||||
{items.map((conf) => (
|
{items.map((conf) => (
|
||||||
<tr key={conf.id}>
|
<tr key={conf.id}>
|
||||||
<td>
|
<td>
|
||||||
{conf.is_closed && <Lock style={{ width: 14, height: 14, marginRight: 6, verticalAlign: '-2px' }} aria-hidden="true" />}
|
{/* На мобильном это единственный вход в редактирование строки (см. .conferences-table
|
||||||
{conf.title ?? 'Без названия'}
|
в admin.css — остальные колонки там скрыты), на десктопе — то же самое, что кнопка
|
||||||
<span style={{ display: 'block', font: 'var(--text-caption)', color: 'var(--color-ink-500)' }}>
|
«Редактировать» правее, просто более крупная область клика. */}
|
||||||
№ {conf.number}
|
<button type="button" className="conf-cell-name" onClick={() => setEditingConference(conf)}>
|
||||||
</span>
|
{conf.is_closed && <Lock style={{ width: 14, height: 14, marginRight: 6, verticalAlign: '-2px' }} aria-hidden="true" />}
|
||||||
|
{conf.title ?? 'Без названия'}
|
||||||
|
<span style={{ display: 'block', font: 'var(--text-caption)', color: 'var(--color-ink-500)' }}>
|
||||||
|
№ {conf.number}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{conf.owner_name ?? '—'}
|
{conf.owner_name ?? '—'}
|
||||||
@@ -443,7 +481,14 @@ export function AdminConferencesTab() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{editingConference && <EditConferenceModal conference={editingConference} onClose={() => setEditingConference(null)} />}
|
{editingConference && (
|
||||||
|
<EditConferenceModal
|
||||||
|
conference={editingConference}
|
||||||
|
onClose={() => setEditingConference(null)}
|
||||||
|
onDelete={() => deleteMutation.mutate(editingConference.id)}
|
||||||
|
deleting={deleteMutation.isPending}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{invitingConference && <InviteModal conference={invitingConference} onClose={() => setInvitingConference(null)} />}
|
{invitingConference && <InviteModal conference={invitingConference} onClose={() => setInvitingConference(null)} />}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export function AdminUsersTab() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="table-card">
|
<div className="table-card users-table">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ export function RoomToolbar({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
{...screenShare.buttonProps}
|
{...screenShare.buttonProps}
|
||||||
className={`tb-btn${screenShare.enabled ? ' is-sharing' : ''}`}
|
className={`tb-btn tb-btn--screenshare${screenShare.enabled ? ' is-sharing' : ''}`}
|
||||||
aria-label={screenShare.enabled ? 'Остановить демонстрацию экрана' : 'Демонстрировать экран'}
|
aria-label={screenShare.enabled ? 'Остановить демонстрацию экрана' : 'Демонстрировать экран'}
|
||||||
>
|
>
|
||||||
<span className="icon-shell">
|
<span className="icon-shell">
|
||||||
@@ -155,7 +155,7 @@ export function RoomToolbar({
|
|||||||
{fullscreenSupported && (
|
{fullscreenSupported && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`tb-btn${fullscreenActive ? ' is-panel-open' : ''}`}
|
className={`tb-btn tb-btn--fullscreen${fullscreenActive ? ' is-panel-open' : ''}`}
|
||||||
aria-label={fullscreenActive ? 'Выйти из полноэкранного режима' : 'Развернуть на весь экран'}
|
aria-label={fullscreenActive ? 'Выйти из полноэкранного режима' : 'Развернуть на весь экран'}
|
||||||
aria-pressed={fullscreenActive}
|
aria-pressed={fullscreenActive}
|
||||||
onClick={onToggleFullscreen}
|
onClick={onToggleFullscreen}
|
||||||
@@ -174,7 +174,7 @@ export function RoomToolbar({
|
|||||||
{pipSupported && (
|
{pipSupported && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`tb-btn${pipActive ? ' is-panel-open' : ''}`}
|
className={`tb-btn tb-btn--pip${pipActive ? ' is-panel-open' : ''}`}
|
||||||
aria-label={pipActive ? 'Свернуть мини-окно' : 'Открыть мини-окно'}
|
aria-label={pipActive ? 'Свернуть мини-окно' : 'Открыть мини-окно'}
|
||||||
aria-pressed={pipActive}
|
aria-pressed={pipActive}
|
||||||
onClick={onTogglePiP}
|
onClick={onTogglePiP}
|
||||||
|
|||||||
@@ -14,6 +14,33 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.admin-page .section-tabs { display: flex; gap: var(--space-2); margin-bottom: var(--space-5); border-bottom: 1px solid var(--color-border); }
|
.admin-page .section-tabs { display: flex; gap: var(--space-2); margin-bottom: var(--space-5); border-bottom: 1px solid var(--color-border); }
|
||||||
|
|
||||||
|
/* Мобильная ширина: 4 вкладки не помещаются в один ряд (см. аудит) —
|
||||||
|
переносим в 2 ряда по 2, а необязательный поясняющий текст под заголовком
|
||||||
|
прячем, чтобы не съедал экран (сам заголовок тянет .page-head h1 из
|
||||||
|
lobby.css). */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.admin-page .page-head p { display: none; }
|
||||||
|
.admin-page .section-tabs { flex-wrap: wrap; }
|
||||||
|
|
||||||
|
/* Таблица «Пользователи» — на мобильном оставляем только колонку
|
||||||
|
«Пользователь»; роль/команда/блокировка редактируются через карточку
|
||||||
|
профиля (открывается кликом по имени, см. AdminUserProfileDialog). */
|
||||||
|
.admin-page .users-table th:not(:first-child),
|
||||||
|
.admin-page .users-table td:not(:first-child) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Таблица «Конференции» — оставляем «Конференция» и «Владелец», статус/
|
||||||
|
тип/начало/действия скрыты; клик по названию (.conf-cell-name) открывает
|
||||||
|
то же редактирование, что раньше было доступно только через карандаш в
|
||||||
|
скрытой колонке действий — там же теперь есть и удаление (см. кнопку
|
||||||
|
«Удалить» в EditConferenceModal). */
|
||||||
|
.admin-page .conferences-table th:not(:nth-child(-n+2)),
|
||||||
|
.admin-page .conferences-table td:not(:nth-child(-n+2)) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
.admin-page .section-tab { padding: 10px 4px; margin-right: var(--space-6); background: none; border: none; border-bottom: 2px solid transparent; font: var(--text-body-lg); font-weight: 600; color: var(--color-ink-500); cursor: pointer; }
|
.admin-page .section-tab { padding: 10px 4px; margin-right: var(--space-6); background: none; border: none; border-bottom: 2px solid transparent; font: var(--text-body-lg); font-weight: 600; color: var(--color-ink-500); cursor: pointer; }
|
||||||
.admin-page .section-tab:hover { color: var(--color-ink-900); }
|
.admin-page .section-tab:hover { color: var(--color-ink-900); }
|
||||||
.admin-page .section-tab.is-active { color: var(--color-ink-700); border-color: var(--color-accent-active); }
|
.admin-page .section-tab.is-active { color: var(--color-ink-700); border-color: var(--color-accent-active); }
|
||||||
@@ -64,6 +91,14 @@
|
|||||||
.admin-page .user-cell-name { background: none; border: none; padding: 0; font: inherit; color: inherit; cursor: pointer; text-align: left; }
|
.admin-page .user-cell-name { background: none; border: none; padding: 0; font: inherit; color: inherit; cursor: pointer; text-align: left; }
|
||||||
.admin-page .user-cell-name:hover { text-decoration: underline; color: var(--color-ink-700); }
|
.admin-page .user-cell-name:hover { text-decoration: underline; color: var(--color-ink-700); }
|
||||||
|
|
||||||
|
/* Название конференции с №, оформленное кнопкой — открывает то же
|
||||||
|
редактирование, что и карандаш в row-actions правее (см.
|
||||||
|
AdminConferencesTab). На мобильном (.conferences-table ниже) это
|
||||||
|
единственный вход в редактирование строки, на десктопе — просто более
|
||||||
|
крупная область клика поверх уже существующей иконки. */
|
||||||
|
.admin-page .conf-cell-name { background: none; border: none; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; text-align: left; display: block; width: 100%; }
|
||||||
|
.admin-page .conf-cell-name:hover { color: var(--color-ink-700); }
|
||||||
|
|
||||||
.admin-page select { font: var(--text-body); color: var(--color-ink-900); padding: 6px 10px; border-radius: var(--radius-md); border: 1px solid var(--color-border); background: var(--color-surface); font-family: var(--font-body); }
|
.admin-page select { font: var(--text-body); color: var(--color-ink-900); padding: 6px 10px; border-radius: var(--radius-md); border: 1px solid var(--color-border); background: var(--color-surface); font-family: var(--font-body); }
|
||||||
.admin-page select:disabled { opacity: .5; cursor: not-allowed; }
|
.admin-page select:disabled { opacity: .5; cursor: not-allowed; }
|
||||||
.admin-page select:focus { outline: none; border-color: var(--color-focus-ring); }
|
.admin-page select:focus { outline: none; border-color: var(--color-focus-ring); }
|
||||||
|
|||||||
@@ -244,7 +244,10 @@
|
|||||||
right: -90px;
|
right: -90px;
|
||||||
bottom: -90px;
|
bottom: -90px;
|
||||||
}
|
}
|
||||||
.brand-headline { max-width: none; }
|
/* clamp() — на узких экранах слово «инфраструктура» иначе вылезает за
|
||||||
|
край брендовой панели (см. .brand-panel padding ниже и её overflow:hidden,
|
||||||
|
обрезающий текст без переноса вместо уменьшения кегля). */
|
||||||
|
.brand-headline { max-width: none; font-size: clamp(22px, 6.2vw, 34px); }
|
||||||
.brand-sub { max-width: none; }
|
.brand-sub { max-width: none; }
|
||||||
.brand-stats { flex-wrap: wrap; }
|
.brand-stats { flex-wrap: wrap; }
|
||||||
.stat-glass { flex: 1 1 140px; min-width: 0; }
|
.stat-glass { flex: 1 1 140px; min-width: 0; }
|
||||||
|
|||||||
@@ -55,6 +55,14 @@
|
|||||||
.field.has-error input { border-color: var(--color-danger); box-shadow: 0 0 0 3px rgba(199, 54, 79, 0.1); }
|
.field.has-error input { border-color: var(--color-danger); box-shadow: 0 0 0 3px rgba(199, 54, 79, 0.1); }
|
||||||
.error-msg { display: flex; align-items: center; gap: 6px; margin-top: var(--space-2); font: var(--text-body); color: var(--color-danger); }
|
.error-msg { display: flex; align-items: center; gap: 6px; margin-top: var(--space-2); font: var(--text-body); color: var(--color-danger); }
|
||||||
|
|
||||||
|
/* Кнопка «Ввести другую ссылку или номер» (экран «конференция завершена») —
|
||||||
|
единственная кнопка на экране БЕЗ формы-обёртки (Продолжить/Войти лежат
|
||||||
|
в <form>, которая как block-элемент задаёт им ширину; здесь у .btn нет
|
||||||
|
родителя-формы). .btn держит white-space: nowrap (shell.css) — на
|
||||||
|
остальных кнопках текст короткий и это не заметно, а здесь длинная фраза
|
||||||
|
выталкивала кнопку за правый край карточки на узких экранах. */
|
||||||
|
.flow-card > .btn-primary { width: 100%; white-space: normal; text-align: center; }
|
||||||
|
|
||||||
.link-back { display: block; text-align: center; margin-top: var(--space-5); font: var(--text-body); font-weight: 600; color: var(--color-ink-500); text-decoration: none; background: none; border: none; width: 100%; cursor: pointer; }
|
.link-back { display: block; text-align: center; margin-top: var(--space-5); font: var(--text-body); font-weight: 600; color: var(--color-ink-500); text-decoration: none; background: none; border: none; width: 100%; cursor: pointer; }
|
||||||
.link-back:hover { color: var(--color-ink-700); }
|
.link-back:hover { color: var(--color-ink-700); }
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ main.page-main { padding: var(--space-8); max-width: 1440px; margin: 0 auto; }
|
|||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
main.page-main { padding: var(--space-5) var(--space-4); }
|
main.page-main { padding: var(--space-5) var(--space-4); }
|
||||||
.page-head { flex-direction: column; align-items: flex-start; gap: var(--space-3); }
|
.page-head { flex-direction: column; align-items: flex-start; gap: var(--space-3); }
|
||||||
|
/* Длинные заголовки («Администрирование» и т.п.) иначе вылезают за край. */
|
||||||
|
.page-head h1 { font-size: clamp(22px, 6.5vw, 34px); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- Лобби-хаб (мозаика действий) ---------- */
|
/* ---------- Лобби-хаб (мозаика действий) ---------- */
|
||||||
|
|||||||
@@ -115,6 +115,26 @@ video[data-lk-source='screen_share'] { object-fit: contain; background: #000; }
|
|||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
.stage-tiles.lk-focus-layout { grid-template-columns: 1fr; }
|
.stage-tiles.lk-focus-layout { grid-template-columns: 1fr; }
|
||||||
.stage { padding: var(--space-3) var(--space-4); gap: var(--space-3); }
|
.stage { padding: var(--space-3) var(--space-4); gap: var(--space-3); }
|
||||||
|
|
||||||
|
/* Топбар: инвайт-чип (номер + копирование ссылки) убираем — на узком
|
||||||
|
экране это не помещалось рядом с названием (см. аудит мобильной версии),
|
||||||
|
сверху остаётся только название конференции и статус «активна». */
|
||||||
|
.room-topbar { padding: var(--space-3) var(--space-4); }
|
||||||
|
.room-invite-chip { display: none; }
|
||||||
|
|
||||||
|
/* Тулбар: демонстрация/полноэкранный режим/мини-окно скрыты на мобильном
|
||||||
|
(редко нужны с телефона, а тулбар со всеми 8 кнопками физически не
|
||||||
|
помещается в ширину экрана) — из оставшихся 5 кнопок собираем более
|
||||||
|
компактный вариант, чтобы они уместились без переноса и горизонтального
|
||||||
|
скролла. */
|
||||||
|
.tb-btn--screenshare,
|
||||||
|
.tb-btn--fullscreen,
|
||||||
|
.tb-btn--pip {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.room-toolbar { padding: var(--space-3) var(--space-2); gap: 2px; }
|
||||||
|
.tb-btn { min-width: 0; padding: 6px 8px; }
|
||||||
|
.tb-btn .icon-shell { width: 40px; height: 40px; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -265,6 +265,23 @@
|
|||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--color-danger);
|
||||||
|
border: 1.5px solid var(--color-danger);
|
||||||
|
font: var(--text-body);
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 9px 18px;
|
||||||
|
border-radius: var(--radius-full);
|
||||||
|
}
|
||||||
|
.btn-danger:hover:not(:disabled) {
|
||||||
|
background: var(--color-danger-bg);
|
||||||
|
}
|
||||||
|
.btn-danger:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------- Бейджи статуса ---------- */
|
/* ---------- Бейджи статуса ---------- */
|
||||||
.badge {
|
.badge {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@@ -393,6 +410,23 @@
|
|||||||
.modal-actions { display: flex; gap: var(--space-3); margin-top: var(--space-5); }
|
.modal-actions { display: flex; gap: var(--space-3); margin-top: var(--space-5); }
|
||||||
.modal-actions .btn { flex: 1; justify-content: center; }
|
.modal-actions .btn { flex: 1; justify-content: center; }
|
||||||
|
|
||||||
|
/* Опасное действие (удаление) — отдельно слева, Отмена/Сохранить — группой
|
||||||
|
справа, без растягивания на всю ширину (см. EditConferenceModal). */
|
||||||
|
.modal-actions.modal-actions--split { justify-content: space-between; }
|
||||||
|
.modal-actions--split > .btn-danger { flex: none; }
|
||||||
|
.modal-actions--split .modal-actions-group { display: flex; gap: var(--space-3); }
|
||||||
|
.modal-actions--split .row-actions { display: flex; align-items: center; gap: var(--space-3); }
|
||||||
|
|
||||||
|
/* Узкий экран: «Удалить» + «Отмена» + «Сохранить» в один ряд не помещаются
|
||||||
|
(в сумме шире содержимого модалки на телефоне) — переносим «Удалить» на
|
||||||
|
свою строку сверху. */
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.modal-actions--split { flex-wrap: wrap; }
|
||||||
|
.modal-actions--split > .btn-danger { flex: 1 1 100%; }
|
||||||
|
.modal-actions--split .modal-actions-group { flex: 1 1 100%; }
|
||||||
|
.modal-actions--split .row-actions { flex-wrap: wrap; }
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------- Поля форм в модалках (общие для всех диалогов оболочки) ---------- */
|
/* ---------- Поля форм в модалках (общие для всех диалогов оболочки) ---------- */
|
||||||
.field { margin-bottom: var(--space-5); }
|
.field { margin-bottom: var(--space-5); }
|
||||||
.field:last-child { margin-bottom: 0; }
|
.field:last-child { margin-bottom: 0; }
|
||||||
|
|||||||
Reference in New Issue
Block a user