diff --git a/frontend/src/components/room/RoomStage.tsx b/frontend/src/components/room/RoomStage.tsx
index 4f4939e..1f85321 100644
--- a/frontend/src/components/room/RoomStage.tsx
+++ b/frontend/src/components/room/RoomStage.tsx
@@ -1,5 +1,5 @@
import { useEffect, useState, type ReactNode } from 'react'
-import { EyeOff, Users } from 'lucide-react'
+import { EyeOff, Mic, MicOff, Users } from 'lucide-react'
import { Track, type Participant } from 'livekit-client'
import {
CarouselLayout,
@@ -8,6 +8,7 @@ import {
isTrackReference,
useRoomContext,
useSpeakingParticipants,
+ useTrackToggle,
useTracks,
type TrackReferenceOrPlaceholder,
} from '@livekit/components-react'
@@ -93,6 +94,29 @@ function hasLiveVideo(track: TrackReferenceOrPlaceholder): boolean {
return isTrackReference(track) && !track.publication.isMuted
}
+/**
+ * Кнопка микрофона поверх единственной плитки мини-плеера (задача A5): в
+ * Document PiP своего тулбара нет вовсе, а звонок продолжается — без неё
+ * замьютить себя можно, только развернув основное окно. `useTrackToggle` —
+ * тот же хук, что и в `RoomToolbar`, читает/меняет состояние ЧЕРЕЗ
+ * `RoomContext`, а не через DOM, поэтому работает одинаково что в основном
+ * окне, что в портале, рендерящем эту сцену внутрь `pipWindow.document.body`
+ * (React-контекст порталом не разрывается, в отличие от DOM-дерева).
+ */
+function PipMicToggle() {
+ const mic = useTrackToggle({ source: Track.Source.Microphone })
+ return (
+
+ )
+}
+
/**
* Основная сцена конференции: превью остальных участников + крупная плитка
* активного спикера (FocusLayoutContainer + CarouselLayout при нескольких
@@ -366,6 +390,7 @@ export function RoomStage({
return (
)
diff --git a/frontend/src/styles/room.css b/frontend/src/styles/room.css
index c4806a2..3e2778c 100644
--- a/frontend/src/styles/room.css
+++ b/frontend/src/styles/room.css
@@ -133,9 +133,32 @@ video[data-lk-source='screen_share'] { object-fit: contain; background: #000; }
* `.stage-tiles` в основном окне) плюс подстраховочный `min-height`, чтобы
* плитка не схлопывалась, если видео ещё не выдало реальные размеры.
*/
-.room-single-tile { flex: 1; min-height: 0; display: flex; }
+.room-single-tile { position: relative; flex: 1; min-height: 0; display: flex; }
.room-single-tile .lk-participant-tile { flex: 1; min-height: 220px; width: 100%; }
+/* Кнопка микрофона мини-плеера (задача A5) — своего тулбара в PiP нет,
+ кнопка лежит поверх единственной плитки левым нижним углом (правый занят
+ штатным `.lk-focus-toggle-button`/нашей булавкой закрепления — тут её нет,
+ в PiP закреплять нечего, но геометрия зеркалит `.room-pin-toggle`). */
+.room-pip-mic-toggle {
+ position: absolute;
+ left: 0.5rem;
+ bottom: 0.5rem;
+ z-index: 5;
+ width: 36px;
+ height: 36px;
+ border-radius: 50%;
+ border: none;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--color-room-mic-on);
+ color: #10331f;
+ cursor: pointer;
+}
+.room-pip-mic-toggle svg { width: 18px; height: 18px; }
+.room-pip-mic-toggle.is-off { background: var(--color-room-danger-bg); color: #fff; }
+
/*
* ---------- Колонка миниатюр (кнопка «Скрыть» + карусель) ----------
* Прямой ребёнок `.lk-focus-layout` — вместо самой карусели, потому что над