/* ╔══════════════════════════════════════════════════════════════════════════════════╗
   ║  callui.css — ВЁРСТКА ЕДИНСТВЕННОЙ ПЛИТКИ СОЗВОНА (пара к callui.js).            ║
   ╚══════════════════════════════════════════════════════════════════════════════════╝
   Эти правила ПЕРЕЕХАЛИ СЮДА из style.css и стали общими: их грузит и приложение
   (index.html), и гостевая страница (guest.html). Раньше каждая поверхность созвона
   рисовала участника своим CSS — `.gtile` в конференции, `.gmav-t` в игровой комнате,
   `.tile` у гостя, — и любая правка вида плитки чинилась в одном месте из трёх.

   🔴 Токены `--gc-*` объявлены с ФОЛБЭКАМИ прямо здесь: плитка обязана выглядеть одинаково
   на любой поверхности, даже там, где нет тёмного оверлея созвона (полоса созвона в игровой
   комнате живёт на светлой теме раздела). Переопределить их поверхность может — но не
   ОБЯЗАНА, иначе «одна реализация» опять развалится на три.

   🔴 Что НЕ здесь: раскладка конкретной поверхности (размер плитки в ленте комнаты, ширина
   плавающего окна, сетка гостевой страницы) — это дело поверхности. Здесь только то, из
   чего состоит САМ участник и как выглядит спотлайт «крупный + лента».

   🔴🔴 ЗВОН-34 · НАД ЖИВЫМ ВИДЕО НЕ ВЕШАЕМ ДОРОГИХ СЛОЁВ. Жалоба «видео собеседника
   дёргается при движении» оказалась не только про сеть: часть рывков делал САМ БРАУЗЕР.
   Chromium под `backdrop-filter` читает текстуру фона обратно из композитора и клипает
   результат по скруглению элемента — поверх ЖИВОГО кадра это работа В ТАКТ КАДРАМ, то
   есть 30-60 раз в секунду, на КАЖДОЙ плитке. Ровно так же платит анимация, меняющая
   `box-shadow`: тень пересчитывается каждый кадр анимации.
   Поэтому в этом файле действует правило:
     • гарантированно дёшевы у композитора ровно два свойства — `transform` и `opacity`.
       Анимировать что-то ещё поверх видео ЗАПРЕЩЕНО;
     • «стекло» (`backdrop-filter`, `filter`, `mask-image`) над кадром ЗАПРЕЩЕНО —
       вместо блюра ставим плотную полупрозрачную подложку. Отличие видно только рядом,
       а рывки видит каждый;
     • `backdrop-filter` в продукте остаётся ТАМ, ГДЕ ПОД НИМ НЕТ ВИДЕО (док управления
       `.groupcall-controls`, боковые панели в style.css): там читается статичный фон, и
       это честное «стекло», а не работа в такт кадрам. В callui.css таких мест НЕТ —
       здесь всё лежит на кадре, поэтому и блюра здесь нет ни одного. */

/* ── сетка равных плиток: JS задаёт --gt-w/--gt-h под оптимальную раскладку ── */
.group-grid {
  display: flex; flex-wrap: wrap; justify-content: center; align-content: center; align-items: center;
  gap: 10px; margin: 0; width: 100%; height: 100%;
}
.group-grid > .gtile { width: var(--gt-w, 240px); height: var(--gt-h, 135px); flex: 0 0 auto; }

/* ── плитка ── */
.gtile {
  position: relative; background: var(--gc-tile, #171a22); border-radius: 14px; overflow: hidden;
  border: 1px solid var(--gc-hair, rgba(255,255,255,.08)); min-height: 0; min-width: 0;
  cursor: pointer; transition: border-color .2s;
}
/* 🎞️ ЗВОН-34 · КАДР ЖИВЁТ СВОИМ СЛОЕМ КОМПОЗИТОРА.
   `will-change: transform` — просьба к браузеру держать декодированный кадр отдельным
   слоем и не подмешивать его в общий растр плитки вместе с подписью, эквалайзером и
   вуалью (иначе любая из них тянет за собой перерисовку 30-60 раз в секунду). Зеркало
   своей камеры (`.gtile-local video`) — ТОТ ЖЕ transform, конфликта нет.
   `contain: paint` — обещание «за пределы этого прямоугольника ничего не вылезет», по
   которому браузер перестаёт проверять кадр на пересечение с соседями.
   🔴 НЕ УБИРАТЬ: именно эта пара разводит «человека в кадре» и «стекляшки поверх него»
   по разным слоям. */
.gtile video {
  width: 100%; height: 100%; object-fit: cover; background: #000; display: block;
  contain: paint; will-change: transform;
}
/* Вуаль под подписью — СТАТИЧЕСКИЙ градиент: он не меняется ни разу за весь созвон.
   `will-change: opacity` просит для неё собственный слой, чтобы непрозрачный прямоугольник
   поверх кадра не заставлял композитор перерисовывать его вместе с каждым кадром видео. */
.gtile::after { content: ""; position: absolute; inset: 0; pointer-events: none; will-change: opacity; background: linear-gradient(180deg, transparent 62%, rgba(0,0,0,.42) 100%); }
.gtile:focus-visible { outline: 2px solid var(--primary, #667eea); outline-offset: 2px; }

/* ═══ M8 · ВОДЯНОЙ ЗНАК поверх кадра (policy.security.watermark) ═══
   «Имя · ЧЧ:ММ» по диагонали: снимок экрана несёт, КТО и КОГДА смотрел. Требования:
     • не мешать смотреть → очень низкий контраст + слой ВЫШЕ скрима .gtile::after,
       но НИЖЕ подписи имени/иконок (z-index 2 против 3);
     • не ловить события и не выделяться мышью (иначе «скопировал знак — убрал знак»);
     • оба места (плитка группы и кадр 1-на-1 в .videos) — ОДИН класс, один вид. */
.gtile .cc-wm, .videos .cc-wm {
  position: absolute; inset: 0; z-index: 2;
  pointer-events: none; user-select: none; -webkit-user-select: none;
  display: flex; flex-direction: column; align-items: center; justify-content: space-evenly;
  overflow: hidden; transform: rotate(-24deg); transform-origin: 50% 50%;
}
.cc-wm span {
  display: block; white-space: nowrap; text-align: center;
  font-size: clamp(10px, 1.9vh, 15px); font-weight: 600; letter-spacing: .08em;
  color: rgba(255,255,255,.16); text-shadow: 0 1px 2px rgba(0,0,0,.30);
}
.videos .cc-wm span { font-size: clamp(12px, 2.4vh, 20px); }
/* На выключенной камере (аватар-заглушка) знак дублировать незачем — там нечего снимать. */
.gtile.cam-off .cc-wm { display: none; }

/* 📶 Точка качества связи участника: появляется ТОЛЬКО на poor/lost — на здоровой плитке
   индикатора нет, экран не занят «на всякий случай». */
.gt-cq {
  position: absolute; top: 9px; left: 9px; z-index: 3; display: none;
  width: 8px; height: 8px; border-radius: 50%; background: var(--warning, #f5a623);
  box-shadow: 0 0 0 3px rgba(9,11,16,.45);
}
.gtile.cq-bad .gt-cq { display: block; }
.gtile.cq-lost .gt-cq { background: var(--danger, #ef4444); animation: gcNetPulse 1.6s ease-in-out infinite; }

/* Своя камера — зеркалим; тыловая (facingMode environment) — БЕЗ зеркала, иначе текст
   в кадре читался бы наоборот. Класс no-mirror ставит переключатель камеры. */
.gtile-local video { transform: scaleX(-1); }
.videos .local.no-mirror,
.gtile-local video.no-mirror { transform: none; }

/* камера выключена → скрыть видео, показать кружок-заглушку */
.gtile.cam-off { background: var(--gc-tile-off, #14161f); }
.gtile.cam-off video { display: none; }
.gt-avatar {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); z-index: 1;
  width: clamp(52px, 11vh, 104px); aspect-ratio: 1; border-radius: 50%;
  display: none; align-items: center; justify-content: center;
  color: #fff; font-weight: 600; letter-spacing: .5px; font-size: clamp(18px, 4.4vh, 38px);
  background-size: cover; background-position: center;
  box-shadow: 0 10px 26px -8px rgba(0,0,0,.55), inset 0 1px 0 rgba(255,255,255,.22);
}
.gtile.cam-off .gt-avatar { display: flex; }
/* Фото участника вместо букв — умела только игровая комната, теперь общее. */
.gt-avatar.photo { color: transparent; }
/* Живой (видео) аватар — умел только экран звонка 1:1, теперь тоже общий (CLIE-606·5). */
.gt-avatar .gt-av-vid {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; border-radius: 50%; display: block;
}

/* ═══ АКТИВНЫЙ СПИКЕР — КОЛЬЦО, КОТОРОЕ НЕ ПЕРЕРИСОВЫВАЕТ ВИДЕО (ЗВОН-34) ═══════════
   БЫЛО: `animation: gcSpeak` меняла САМ `box-shadow` плитки. То есть пока человек
   говорит (а говорит он бóльшую часть созвона), браузер 2.4 с в цикле заново растрировал
   тень ВОКРУГ живого кадра — на каждой говорящей плитке. Тень к дешёвым свойствам
   композитора не относится: дёшевы ровно `transform` и `opacity`.
   СТАЛО: тень СТАТИЧЕСКАЯ (растрируется один раз при появлении класса), а «дыхание»
   даёт отдельный слой `::before`, у которого меняется ТОЛЬКО `opacity`.
   🔴 ПОЧЕМУ КОЛЬЦО НА ПСЕВДОЭЛЕМЕНТЕ — ВНУТРЕННЕЕ (`inset: 0` + inset-тени), а не
   `inset: -2px` с наружной тенью: у `.gtile` стоит `overflow: hidden` (им скругляется
   кадр), и ЛЮБОЙ потомок, вылезший за padding-box, обрезается вместе со своей тенью —
   наружное кольцо на псевдоэлементе просто не было бы видно. Наружный ореол умеет
   рисовать только сама плитка (собственный box-shadow элемента не клипается его же
   overflow) — поэтому он и остался на ней, но уже неподвижным. */
.gtile.speaking {
  border-color: transparent;
  box-shadow: 0 0 0 2px var(--primary, #667eea), 0 0 0 5px rgba(102,126,234,.18), 0 0 22px 1px rgba(102,126,234,.30);
}
.gtile.speaking::before {
  content: ""; position: absolute; inset: 0; z-index: 2; pointer-events: none;
  border-radius: inherit;
  box-shadow: inset 0 0 0 2px var(--primary-2, #8b9cf5), inset 0 0 20px 2px rgba(139,156,245,.34);
  will-change: opacity;
  animation: gcSpeak 2.4s ease-in-out infinite;
}
@keyframes gcSpeak {
  0%,100% { opacity: .16; }
  50%     { opacity: 1; }
}
/* Поверхность может ВЫКЛЮЧИТЬ кольцо говорящего целиком: кадр «во всю сцену» (эфир у
   гостя) не должен дышать рамкой. Раньше для этого хватало `animation: none` на самой
   плитке — теперь пульс живёт отдельным слоем, поэтому у канона есть ЯВНЫЙ выключатель,
   а не «допишите ещё одно правило у себя». Ставит его поверхность (guest.js → streamLayout).
   🔴 Стоит НИЖЕ `.gtile.speaking`: специфичность та же, решает порядок. */
.gtile.no-ring, .gtile.no-ring.speaking { box-shadow: none; }
.gtile.no-ring.speaking::before { display: none; }
/* эквалайзер-бейдж у говорящего.
   🔴 Подложка ПЛОТНАЯ, без `backdrop-filter` (ЗВОН-34): бейдж висит на живом кадре и
   виден ровно тогда, когда человек говорит, — блюр здесь стоил бы readback фона каждый
   кадр. .5 + блюр заменены на .72: на светлом кадре палочки эквалайзера остаются
   читаемыми, на тёмном разница с прежним видом неразличима. */
.gt-eq { position: absolute; top: 9px; right: 9px; z-index: 3; display: none; align-items: flex-end; gap: 2.5px; height: 14px; padding: 6px 7px; border-radius: 7px; background: rgba(9,11,16,.72); }
.gtile.speaking .gt-eq { display: flex; }
.gt-eq i { width: 2.5px; background: var(--primary-2, #8b9cf5); border-radius: 2px; animation: gcEq .9s ease-in-out infinite; }
.gt-eq i:nth-child(1){ height: 5px; animation-delay: 0s; }
.gt-eq i:nth-child(2){ height: 13px; animation-delay: .15s; }
.gt-eq i:nth-child(3){ height: 8px; animation-delay: .3s; }
@keyframes gcEq { 0%,100%{ transform: scaleY(.4); } 50%{ transform: scaleY(1); } }

/* кнопка «свернуть» на главном тайле спотлайта (вернуть в сетку / снять закреп).
   🔴 Плотная подложка вместо блюра (ЗВОН-34): кнопка живёт на САМОМ КРУПНОМ кадре
   созвона — том, который видно лучше всего и который дёргается заметнее всего.
   .6 + блюр → .78: белая иконка одинаково читается и на снегу, и на тёмной комнате. */
.gt-collapse {
  position: absolute; top: 8px; right: 8px; z-index: 4; display: none;
  width: 30px; height: 30px; padding: 0; border-radius: 50%; align-items: center; justify-content: center;
  background: rgba(9,11,16,.78);
  color: #fff; border: 1px solid var(--gc-hair, rgba(255,255,255,.08)); cursor: pointer;
}
.gt-collapse svg.ic { width: 16px; height: 16px; }
.group-grid.spotlight .gtile.main .gt-collapse { display: flex; }
.group-grid.spotlight .gtile.main.speaking .gt-eq { right: 46px; }

/* имя-пилюля + значок «микрофон выключен».
   🔴 САМОЕ ДОРОГОЕ ИЗ ТРЁХ БЫЛО ЗДЕСЬ (ЗВОН-34): подпись видна ВСЕГДА и на КАЖДОЙ плитке,
   то есть `backdrop-filter` радиусом 10px читал фон обратно из композитора в такт кадрам —
   столько раз, сколько в созвоне участников. Заменён плотной подложкой .74 (было .6 +
   блюр). Хэйрлайн-рамка и радиус те же, поэтому «стеклянность» на глаз сохраняется.
   Цвет НЕ зависит от темы приложения и это осознанно: пилюля лежит на ВИДЕОКАДРЕ, а не
   на холсте раздела, — в светлой теме под ней ровно тот же кадр, что и в тёмной. */
.gname {
  position: absolute; left: 10px; bottom: 10px; z-index: 3; max-width: calc(100% - 20px);
  display: inline-flex; align-items: center; gap: 6px; height: 26px; padding: 0 10px; border-radius: 8px;
  background: rgba(9,11,16,.74);
  border: 1px solid rgba(255,255,255,.07); font-size: 12.5px; font-weight: 500; line-height: 1; color: #fff;
}
.gt-name-txt { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.gt-mic { display: none; flex: 0 0 auto; color: #ff8f8f; }
.gt-mic svg.ic { width: 14px; height: 14px; display: block; }
.gtile.mic-off .gt-mic { display: inline-flex; }

/* Демонстрация экрана — показываем ВЕСЬ кадр (object-fit:contain), чтобы ничего не обрезалось.
   Демонстрация приоритетнее «камера выключена»: всегда показываем экран, аватар прячем. */
.gtile.screen-share video { object-fit: contain; background: #000; display: block; }
.gtile.screen-share .gt-avatar { display: none; }

/* ── СПОТЛАЙТ: большой главный кадр + лента миниатюр («как в зум») ──
   ЭТО И ЕСТЬ «крупно», а не второй <video> под своей сценой: главный кадр — та же плитка,
   только с классом .main. Одна дорожка декодируется один раз. */
.group-grid.spotlight { display: flex; flex-wrap: wrap; align-content: flex-start; gap: 10px; align-items: stretch; width: 100%; height: 100%; overflow: auto; }
.group-grid.spotlight .gtile { flex: 0 0 clamp(120px, 15vw, 190px); height: clamp(76px, 11vh, 104px); min-height: 0; aspect-ratio: auto; order: 0; }
/* Главный тайл — на всю высоту минус лента миниатюр (её высота + зазор). */
.group-grid.spotlight .gtile.main { flex: 1 1 100%; height: calc(100% - clamp(76px, 11vh, 104px) - 12px); min-height: 180px; aspect-ratio: auto; order: -1; }
/* аватар в миниатюре ленты — компактнее (иначе диск вылезает за низкий тайл) */
.group-grid.spotlight .gtile:not(.main) .gt-avatar { width: clamp(32px, 7vh, 52px); font-size: clamp(13px, 2.6vh, 20px); box-shadow: none; }
