Кнопка «Фон» в тулбаре комнаты и выбор фона в превью на входе: три готовые
сцены и свои картинки из профиля. Фон применяется процессором к самому
публикуемому треку (`LocalVideoTrack.setProcessor`), а НЕ пересозданием
`RoomOptions` — ссылка на них обязана оставаться стабильной, иначе
`LiveKitRoom` переподключается к комнате.
Фича только для ДЕСКТОПА, и «десктоп» определяется по возможностям устройства
(`pointer: fine` + `hover: hover` + `maxTouchPoints`), а НЕ по ширине окна:
узкое окно на десктопе — всё ещё десктоп, а широкий планшет — всё ещё планшет,
который сегментация греет. На мобильном кнопки нет вовсе, а не задизейбленной.
Ассеты сегментации отдаются СО СВОЕГО домена: библиотека по умолчанию тянет
wasm с jsdelivr, а модель с storage.googleapis.com, и в закрытом контуре фича
молча не работала бы. Модель (Apache 2.0, см. NOTICE.txt) лежит в репозитории,
wasm-рантайм (~19 МБ) копируется из node_modules плагином сборки. Сама
библиотека и модель грузятся ЛЕНИВО — только когда фон реально включают, вход
в конференцию не стал медленнее.
Три дефолтные сцены — собственные векторные рисунки (`design/backgrounds/`),
а не фотографии из интернета: у нарисованной сцены нет чужой лицензии, а
продукт расходится по инсталляциям, и проверять права на каждую копию некому.
Свои картинки — в профиле, до 10 штук, с уменьшением до 1280px и переводом в
WebP прямо в браузере перед отправкой. Удаление применённого сейчас фона
сбрасывает выбор на «без фона»: хранится ключ записи, а не URL картинки.
Смена камеры фон не теряет (`restartTrack` перезапускает процессор сам),
выключение и включение камеры — навешивает его на новый трек заново.
⚠️ Прокси dev-сервера для `/media/` — обязательно со слэшем: ключ `/media`
Vite матчит префиксом и перехватывает заодно `/mediapipe/...`, из-за чего
модель получала 404 и фон молча не включался.
125 lines
4.3 KiB
CSS
125 lines
4.3 KiB
CSS
/*
|
||
* VidConf — выбор фона видео (сессия 35).
|
||
*
|
||
* Одна сетка плиток на три места: превью входа (`JoinPage`), комната
|
||
* (`BackgroundDialog`) и профиль. Базовые правила — на токенах оболочки;
|
||
* внутри комнаты те же классы перекрашиваются блоком `[data-theme="room"]`
|
||
* в конце файла, как это уже сделано для модалок (см. `room.css`,
|
||
* `.room-modal-*`): токены `--color-ink-*`/`--color-border` внутри комнаты
|
||
* не переопределяются, и без этого блока сетка была бы светлой на тёмном.
|
||
*/
|
||
|
||
.bg-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
|
||
gap: var(--space-3);
|
||
}
|
||
|
||
.bg-tile {
|
||
position: relative;
|
||
aspect-ratio: 16 / 9;
|
||
padding: 0;
|
||
border: 2px solid var(--color-border);
|
||
border-radius: var(--radius-md);
|
||
background: var(--color-bg-alt);
|
||
overflow: hidden;
|
||
cursor: pointer;
|
||
display: block;
|
||
width: 100%;
|
||
}
|
||
.bg-tile img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
display: block;
|
||
}
|
||
.bg-tile:hover { border-color: var(--color-border-strong); }
|
||
.bg-tile.is-selected { border-color: var(--color-border-pill); }
|
||
.bg-tile:focus-visible { outline: 2px solid var(--color-border-pill); outline-offset: 2px; }
|
||
|
||
/* Плитка «без фона» — перечёркнутый круг вместо картинки. */
|
||
.bg-tile-none {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: var(--color-ink-500);
|
||
}
|
||
.bg-tile-none .lucide { width: 20px; height: 20px; }
|
||
|
||
/* Метка выбранной плитки: галочка либо спиннер, пока фон применяется. */
|
||
.bg-tile-mark {
|
||
position: absolute;
|
||
right: 4px;
|
||
bottom: 4px;
|
||
width: 22px;
|
||
height: 22px;
|
||
border-radius: 50%;
|
||
background: var(--color-border-pill);
|
||
color: var(--color-surface);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.bg-tile-mark .lucide { width: 14px; height: 14px; }
|
||
.bg-tile-spinner { animation: bg-tile-spin 1s linear infinite; }
|
||
@keyframes bg-tile-spin {
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.bg-tile-spinner { animation: none; }
|
||
}
|
||
|
||
/* Подсказка под сеткой — тот же тон, что у `.field-hint` оболочки. */
|
||
.bg-hint { font: var(--text-body); color: var(--color-ink-500); margin: var(--space-2) 0 0; }
|
||
|
||
/* Управление своими картинками (профиль): плитка + кнопка удаления поверх. */
|
||
.bg-manage-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(112px, 1fr));
|
||
gap: var(--space-3);
|
||
}
|
||
.bg-manage-tile {
|
||
position: relative;
|
||
aspect-ratio: 16 / 9;
|
||
border: 1px solid var(--color-border);
|
||
border-radius: var(--radius-md);
|
||
overflow: hidden;
|
||
background: var(--color-bg-alt);
|
||
}
|
||
.bg-manage-tile img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||
.bg-tile-delete {
|
||
position: absolute;
|
||
top: 4px;
|
||
right: 4px;
|
||
width: 24px;
|
||
height: 24px;
|
||
border: none;
|
||
border-radius: 50%;
|
||
background: rgba(0, 0, 0, 0.55);
|
||
color: #fff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
}
|
||
.bg-tile-delete .lucide { width: 13px; height: 13px; }
|
||
.bg-tile-delete:hover { background: var(--color-danger); }
|
||
.bg-tile-delete:disabled { opacity: 0.6; cursor: default; }
|
||
|
||
/* ---------- Та же сетка внутри комнаты (тёмная тема) ---------- */
|
||
[data-theme='room'] .bg-tile {
|
||
border-color: var(--color-room-tile-border);
|
||
background: var(--color-room-tile);
|
||
}
|
||
[data-theme='room'] .bg-tile:hover { border-color: var(--color-room-text-secondary); }
|
||
[data-theme='room'] .bg-tile.is-selected { border-color: var(--color-room-speaker-ring); }
|
||
[data-theme='room'] .bg-tile:focus-visible { outline-color: var(--color-room-focus-ring); }
|
||
[data-theme='room'] .bg-tile-none { color: var(--color-room-text-secondary); }
|
||
[data-theme='room'] .bg-tile-mark {
|
||
background: var(--color-room-speaker-ring);
|
||
color: var(--color-room-bg);
|
||
}
|
||
[data-theme='room'] .bg-hint { color: var(--color-room-text-secondary); }
|