Fix overlapping speaker cards in audio rooms with a proper grid layout.
Deploy Ladill Meet / deploy (push) Successful in 41s

Use display grid for speaker tiles, show only host/co-host/panelist on stage, and re-layout when roles change so cards stay in separate cells.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-04 00:53:55 +00:00
co-authored by Cursor
parent 2fae2e7ed8
commit 1d1a4cd877
3 changed files with 98 additions and 5 deletions
+46 -3
View File
@@ -336,10 +336,53 @@ html.meet-room-page {
} }
.meet-room-page .meet-audio-grid { .meet-room-page .meet-audio-grid {
place-content: center; display: grid;
place-items: center; width: 100%;
grid-template-columns: repeat(auto-fill, minmax(7rem, 9rem)); min-height: 0;
overflow-x: hidden;
overflow-y: auto;
align-content: start;
justify-content: center;
gap: 1rem; gap: 1rem;
padding: 0.75rem;
grid-template-columns: repeat(2, minmax(0, 9.5rem));
}
@media (min-width: 640px) {
.meet-room-page .meet-audio-grid {
gap: 1.25rem;
padding: 1rem;
grid-template-columns: repeat(auto-fill, minmax(8.5rem, 10rem));
align-content: center;
}
}
.meet-room-page .meet-audio-grid.meet-audio-grid--solo {
grid-template-columns: minmax(0, 10rem);
place-content: center;
}
.meet-room-page .meet-audio-grid [data-participant-tile] {
position: relative;
width: 100%;
max-width: 10rem;
justify-self: center;
isolation: isolate;
}
.meet-room-page .meet-audio-tile {
width: 100%;
min-width: 0;
box-shadow: 0 4px 14px rgb(0 0 0 / 0.25);
}
.meet-room-page .meet-audio-tile [data-tile-body] {
flex: 1 1 auto;
min-height: 0;
}
.meet-room-page .meet-audio-tile [data-tile-footer] {
flex-shrink: 0;
} }
.meet-room-page .meet-audio-tile [data-tile-avatar-circle] { .meet-room-page .meet-audio-tile [data-tile-avatar-circle] {
+51 -1
View File
@@ -903,6 +903,13 @@ function meetRoom() {
getTileContainerForIdentity(identity) { getTileContainerForIdentity(identity) {
if (!this.usesStageLayout) { if (!this.usesStageLayout) {
if (this.isSpace) {
const person = this.sessionParticipants.find((entry) => entry.uuid === identity);
if (!person || !['host', 'co_host', 'panelist'].includes(person.role)) {
return null;
}
}
return document.getElementById('video-grid'); return document.getElementById('video-grid');
} }
@@ -980,6 +987,30 @@ function meetRoom() {
if (!this.usesStageLayout) { if (!this.usesStageLayout) {
document.getElementById('meet-stage-spotlight')?.replaceChildren(); document.getElementById('meet-stage-spotlight')?.replaceChildren();
document.getElementById('meet-audience-strip')?.replaceChildren(); document.getElementById('meet-audience-strip')?.replaceChildren();
if (room) {
const all = [room.localParticipant, ...room.remoteParticipants.values()];
all.forEach((participant) => {
const target = this.getTileContainerForIdentity(participant.identity);
if (!target) {
this.removeParticipantTile(participant.identity);
return;
}
const tile = this.getOrCreateParticipantTile(participant);
if (!tile) {
return;
}
if (tile.parentElement !== target) {
target.appendChild(tile);
}
this.updateParticipantTile(participant);
});
}
this.updateAudioGridLayout();
return; return;
} }
@@ -1292,7 +1323,7 @@ function meetRoom() {
tile = document.createElement('div'); tile = document.createElement('div');
tile.dataset.participantTile = identity; tile.dataset.participantTile = identity;
tile.className = this.audioOnly tile.className = this.audioOnly
? 'meet-audio-tile relative flex aspect-square w-full max-w-[9rem] flex-col overflow-hidden rounded-2xl bg-slate-900' ? 'meet-audio-tile relative flex aspect-square w-full flex-col overflow-hidden rounded-2xl bg-slate-900'
: 'relative flex aspect-video w-full flex-col overflow-hidden rounded-xl bg-slate-900 min-h-0'; : 'relative flex aspect-video w-full flex-col overflow-hidden rounded-xl bg-slate-900 min-h-0';
tile.innerHTML = ` tile.innerHTML = `
<div data-tile-body class="relative min-h-0 flex-1"> <div data-tile-body class="relative min-h-0 flex-1">
@@ -2030,9 +2061,20 @@ function meetRoom() {
this.applyStageLayout(); this.applyStageLayout();
this.updateGalleryGridLayout(); this.updateGalleryGridLayout();
this.updateAudioGridLayout();
this.syncActiveSpeakers(room.activeSpeakers ?? []); this.syncActiveSpeakers(room.activeSpeakers ?? []);
}, },
updateAudioGridLayout() {
const grid = document.getElementById('video-grid');
if (!grid || !this.audioOnly || this.usesStageLayout) {
return;
}
const count = grid.querySelectorAll('[data-participant-tile]').length;
grid.classList.toggle('meet-audio-grid--solo', count === 1);
},
updateGalleryGridLayout() { updateGalleryGridLayout() {
const grid = document.getElementById('video-grid'); const grid = document.getElementById('video-grid');
if (!grid || this.usesStageLayout || this.audioOnly) { if (!grid || this.usesStageLayout || this.audioOnly) {
@@ -2060,6 +2102,14 @@ function meetRoom() {
} }
}); });
if (room) {
[room.localParticipant, ...room.remoteParticipants.values()].forEach((participant) => {
if (!this.getTileContainerForIdentity(participant.identity)) {
this.removeParticipantTile(participant.identity);
}
});
}
this.applyStageLayout(); this.applyStageLayout();
}, },
+1 -1
View File
@@ -330,7 +330,7 @@
<main class="relative flex min-h-0 min-w-0 flex-1 flex-col p-4 pb-2 meet-room-main"> <main class="relative flex min-h-0 min-w-0 flex-1 flex-col p-4 pb-2 meet-room-main">
<div id="meet-stage" class="flex h-full min-h-0 flex-1 flex-col gap-3" :class="usesStageLayout ? 'meet-stage meet-stage--' + stageLayout : ''"> <div id="meet-stage" class="flex h-full min-h-0 flex-1 flex-col gap-3" :class="usesStageLayout ? 'meet-stage meet-stage--' + stageLayout : ''">
<div id="meet-stage-spotlight" class="meet-stage-spotlight relative min-h-0 flex-1 overflow-hidden rounded-xl bg-slate-900"></div> <div id="meet-stage-spotlight" class="meet-stage-spotlight relative min-h-0 flex-1 overflow-hidden rounded-xl bg-slate-900"></div>
<div id="video-grid" class="min-h-0 h-full" :class="audioOnly ? 'meet-audio-grid' : (usesStageLayout ? 'meet-stage-panel-grid flex-1' : 'meet-gallery-grid')"></div> <div id="video-grid" class="min-h-0 min-w-0" :class="audioOnly ? 'meet-audio-grid flex-1' : (usesStageLayout ? 'meet-stage-panel-grid flex-1 h-full' : 'meet-gallery-grid h-full')"></div>
<div id="meet-audience-strip" class="meet-audience-strip shrink-0"></div> <div id="meet-audience-strip" class="meet-audience-strip shrink-0"></div>
</div> </div>
<div id="screen-share" class="absolute inset-4 hidden rounded-xl bg-black"></div> <div id="screen-share" class="absolute inset-4 hidden rounded-xl bg-black"></div>