Restyle audio room speaker cards like Spaces and add room chat to the toolbar.
Deploy Ladill Meet / deploy (push) Successful in 49s

Use a three-column avatar grid with role labels and speaking/muted indicators, enable chat in audio rooms, and swap the mobile People nav button for Chat since People stays in the header.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-04 01:10:12 +00:00
co-authored by Cursor
parent 0e3e940ad4
commit 777a3e9126
6 changed files with 150 additions and 28 deletions
+62 -13
View File
@@ -711,7 +711,10 @@ function meetRoom() {
const displayName = this.displayNameForParticipant(participant);
const avatarUrl = this.avatarUrlForParticipant(participant);
const isAudioTile = tile.classList.contains('meet-audio-tile');
const sizeClass = isAudioTile ? 'h-[4.5rem] w-[4.5rem] text-xl' : 'h-20 w-20 text-2xl';
const isSpaceTile = tile.classList.contains('meet-space-tile');
const sizeClass = isSpaceTile
? 'h-[4.75rem] w-[4.75rem] text-lg sm:h-[5.25rem] sm:w-[5.25rem] sm:text-xl'
: isAudioTile ? 'h-[4.5rem] w-[4.5rem] text-xl' : 'h-20 w-20 text-2xl';
circle.replaceChildren();
@@ -989,6 +992,35 @@ function meetRoom() {
return room.remoteParticipants.get(identity) || null;
},
spaceRoleLabel(role) {
return {
host: 'Host',
co_host: 'Co-host',
panelist: 'Speaker',
}[role] || '';
},
spaceSpeakerTileShellHtml() {
return `
<div class="flex w-full justify-center">
<div data-tile-avatar-circle class="flex h-[4.75rem] w-[4.75rem] shrink-0 items-center justify-center rounded-full text-lg font-semibold text-white"></div>
</div>
<span data-tile-name class="mt-2 max-w-full truncate text-center text-sm font-bold leading-tight text-white"></span>
<div data-tile-role-row class="mt-1 flex max-w-full items-center justify-center gap-1">
<span data-tile-speaking class="hidden shrink-0 text-sky-400" title="Speaking">
<svg class="h-3.5 w-3.5" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
<rect class="meet-speaking-bar meet-speaking-bar--1" x="1" y="5" width="2.5" height="6" rx="1"/>
<rect class="meet-speaking-bar meet-speaking-bar--2" x="6.75" y="3" width="2.5" height="10" rx="1"/>
<rect class="meet-speaking-bar meet-speaking-bar--3" x="12.5" y="6" width="2.5" height="4" rx="1"/>
</svg>
</span>
<span data-tile-mic class="hidden shrink-0 text-slate-500" title="Muted">
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 18.75a6 6 0 0 0 6-6v-1.5m-6 7.5a6 6 0 0 1-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 0 1-3-3V4.5a3 3 0 0 1 6 0v8.25a3 3 0 0 1-3 3Z"/><path stroke-linecap="round" stroke-linejoin="round" d="m3 3 18 18"/></svg>
</span>
<span data-tile-role class="truncate text-xs text-slate-400"></span>
</div>`;
},
participantTileShellHtml() {
return `
<div data-tile-body class="relative min-h-0 flex-1">
@@ -1019,14 +1051,26 @@ function meetRoom() {
ensureParticipantTileShell(identity) {
let tile = this.findParticipantTile(identity);
const wantsSpaceTile = this.isSpace && !this.usesStageLayout;
if (tile && wantsSpaceTile !== tile.classList.contains('meet-space-tile')) {
tile.remove();
tile = null;
}
if (!tile) {
tile = document.createElement('div');
tile.dataset.participantTile = identity;
tile.className = this.audioOnly
? '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';
tile.innerHTML = this.participantTileShellHtml();
if (wantsSpaceTile) {
tile.className = 'meet-space-tile flex w-full min-w-0 flex-col items-center px-1 py-1';
tile.innerHTML = this.spaceSpeakerTileShellHtml();
} else {
tile.className = this.audioOnly
? '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';
tile.innerHTML = this.participantTileShellHtml();
}
}
return tile;
@@ -1040,15 +1084,24 @@ function meetRoom() {
const pseudoParticipant = { identity: person.uuid, name: person.display_name };
this.updateTileAvatarCircle(tile, pseudoParticipant);
tile.querySelector('[data-tile-name]').textContent = person.display_name;
const speaking = this.isSpeaking(person.uuid);
tile.classList.toggle('meet-tile-speaking', speaking);
tile.querySelector('[data-tile-speaking]')?.classList.toggle('hidden', !speaking);
const muted = liveParticipant
? !liveParticipant.isMicrophoneEnabled
: Boolean(person.is_muted);
if (tile.classList.contains('meet-space-tile')) {
tile.querySelector('[data-tile-name]').textContent = person.display_name;
tile.querySelector('[data-tile-role]').textContent = this.spaceRoleLabel(person.role);
tile.classList.toggle('meet-tile-speaking', speaking);
tile.querySelector('[data-tile-speaking]')?.classList.toggle('hidden', !speaking);
tile.querySelector('[data-tile-mic]')?.classList.toggle('hidden', !muted || speaking);
return;
}
tile.querySelector('[data-tile-name]').textContent = person.display_name;
tile.classList.toggle('meet-tile-speaking', speaking);
tile.querySelector('[data-tile-speaking]')?.classList.toggle('hidden', !speaking);
tile.querySelector('[data-tile-mic]')?.classList.toggle('hidden', !muted);
tile.querySelector('[data-tile-cam]')?.classList.add('hidden');
},
@@ -2755,10 +2808,6 @@ function meetRoom() {
},
toggleChat() {
if (this.isSpace) {
return;
}
this.toggleSidebarPanel('chat');
},