Fix participant card layout and add meeting video pop-out.
Deploy Ladill Meet / deploy (push) Successful in 56s

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-11 16:41:43 +00:00
co-authored by Cursor
parent 86d5b43607
commit aa16b31fe1
3 changed files with 166 additions and 12 deletions
+48 -9
View File
@@ -62,16 +62,28 @@ html.meet-room-page {
margin-left: auto;
}
.meet-room-page .meet-tile-speaking {
box-shadow: 0 0 0 2px rgb(52 211 153 / 0.9);
.meet-room-page [data-participant-tile].meet-tile-speaking::after {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
box-shadow: inset 0 0 0 2px rgb(52 211 153 / 0.9);
pointer-events: none;
z-index: 4;
}
.meet-room-page .meet-stage-spotlight:has(.meet-tile-speaking) {
box-shadow: 0 0 0 2px rgb(52 211 153 / 0.9);
.meet-room-page .meet-stage-spotlight:has(.meet-tile-speaking)::after {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
box-shadow: inset 0 0 0 2px rgb(52 211 153 / 0.9);
pointer-events: none;
z-index: 4;
}
.meet-room-page .meet-stage-spotlight .meet-tile-speaking {
box-shadow: none;
.meet-room-page .meet-stage-spotlight .meet-tile-speaking::after {
display: none;
}
.meet-room-page #meet-stage:not(.meet-stage) #meet-stage-spotlight,
@@ -123,6 +135,7 @@ html.meet-room-page {
}
.meet-room-page [data-participant-tile] {
position: relative;
display: flex;
flex-direction: column;
min-height: 0;
@@ -237,6 +250,22 @@ html.meet-room-page {
position: relative;
min-height: 0;
flex: 1 1 0;
overflow: hidden;
}
.meet-room-page [data-tile-stage] {
overflow: hidden;
}
.meet-room-page [data-tile-stage] video,
.meet-room-page [data-tile-stage] .meet-tile-video {
display: block;
width: 100%;
height: 100%;
max-width: 100%;
max-height: 100%;
object-fit: cover;
object-position: center;
}
.meet-room-page [data-tile-footer] {
@@ -396,12 +425,22 @@ html.meet-room-page {
box-shadow: none;
}
.meet-room-page .meet-space-tile.meet-tile-speaking {
box-shadow: none;
.meet-room-page .meet-space-tile.meet-tile-speaking::after {
display: none;
}
.meet-room-page .meet-space-tile.meet-tile-speaking [data-tile-avatar-circle] {
box-shadow: 0 0 0 2px rgb(56 189 248 / 0.85);
position: relative;
}
.meet-room-page .meet-space-tile.meet-tile-speaking [data-tile-avatar-circle]::after {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
box-shadow: inset 0 0 0 2px rgb(56 189 248 / 0.85);
pointer-events: none;
z-index: 1;
}
.meet-room-page .meet-space-tile--listener [data-tile-avatar-circle] {
+108 -3
View File
@@ -177,6 +177,8 @@ function meetRoom() {
activeSpeakerIds: [],
floatingReactions: [],
reactionsInitialized: false,
pictureInPictureActive: false,
pictureInPictureSupported: false,
pollTimer: null,
config: {},
@@ -313,6 +315,8 @@ function meetRoom() {
this.syncToolbarConditionalVisibility();
document.querySelector('.meet-toolbar__track')?.classList.add('is-ready');
});
this.setupPictureInPicture();
},
connectionErrorMessage(error) {
@@ -1054,8 +1058,8 @@ function meetRoom() {
participantTileShellHtml() {
return `
<div data-tile-body class="relative min-h-0 flex-1">
<div data-tile-stage class="absolute inset-0 flex items-center justify-center"></div>
<div data-tile-body class="relative min-h-0 flex-1 overflow-hidden">
<div data-tile-stage class="absolute inset-0 overflow-hidden"></div>
<div data-tile-avatar class="absolute inset-0 flex items-center justify-center">
<div data-tile-avatar-circle class="flex h-20 w-20 shrink-0 items-center justify-center rounded-full text-2xl font-semibold text-white shadow-lg"></div>
</div>
@@ -1682,7 +1686,7 @@ function meetRoom() {
stage.innerHTML = '';
const el = track.attach();
el.className = 'h-full w-full object-cover';
el.className = 'meet-tile-video';
el.dataset.participant = participant.identity;
stage.appendChild(el);
},
@@ -2488,6 +2492,107 @@ function meetRoom() {
}
},
setupPictureInPicture() {
this.pictureInPictureSupported = typeof document !== 'undefined'
&& 'pictureInPictureEnabled' in document
&& document.pictureInPictureEnabled !== false;
if (!this.pictureInPictureSupported) {
return;
}
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
this.maybeAutoEnterPictureInPicture();
}
});
document.addEventListener('leavepictureinpicture', () => {
this.pictureInPictureActive = false;
});
},
showPictureInPictureControl() {
return this.pictureInPictureSupported && !this.audioOnly;
},
findPictureInPictureVideo() {
if (this.isScreenSharing) {
const screenVideo = document.querySelector('#screen-share video');
if (screenVideo) {
return screenVideo;
}
}
const localId = this.config.participantUuid;
const priorities = [];
if (room?.localParticipant?.isCameraEnabled) {
priorities.push(localId);
}
this.activeSpeakerIds.forEach((id) => {
if (!priorities.includes(id)) {
priorities.push(id);
}
});
for (const id of priorities) {
const video = this.findParticipantTile(id)?.querySelector('[data-tile-stage] video');
if (video) {
return video;
}
}
return document.querySelector('[data-participant-tile] [data-tile-stage] video');
},
async maybeAutoEnterPictureInPicture() {
if (!this.pictureInPictureSupported || this.pictureInPictureActive || this.audioOnly) {
return;
}
if (document.pictureInPictureElement) {
return;
}
await this.enterPictureInPicture({ silent: true });
},
async togglePictureInPicture() {
if (!this.pictureInPictureSupported) {
return;
}
if (document.pictureInPictureElement) {
await document.exitPictureInPicture();
this.pictureInPictureActive = false;
return;
}
await this.enterPictureInPicture();
},
async enterPictureInPicture({ silent = false } = {}) {
const video = this.findPictureInPictureVideo();
if (!video) {
if (!silent) {
this.connectionStatus = 'No video available to pop out';
}
return;
}
try {
await video.requestPictureInPicture();
this.pictureInPictureActive = true;
} catch {
if (!silent) {
this.connectionStatus = 'Pop-out blocked — try again from the toolbar';
}
}
},
async raiseHand() {
await fetch(this.config.raiseHandUrl, {
method: 'POST',
+10
View File
@@ -427,6 +427,16 @@
title="Share screen">
@include('meet.room.partials.meet-icon', ['icon' => 'screen-share'])
</button>
<button @click="togglePictureInPicture()" type="button"
x-show="showPictureInPictureControl()"
data-alpine-toolbar
class="rounded-full p-3 transition-colors"
:class="pictureInPictureActive ? 'bg-emerald-600 hover:bg-emerald-500' : 'bg-slate-700 hover:bg-slate-600'"
title="Pop out meeting video">
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M7.5 4.5h9a3 3 0 0 1 3 3v9m-12-12v12a3 3 0 0 0 3 3h9M15.75 9.75h4.5v4.5"/>
</svg>
</button>
<button @click="raiseHand()" type="button"
x-show="showPrimaryToolbarAudienceControls()"
data-toolbar-conditional