diff --git a/web/zebra-spaces.html b/web/zebra-spaces.html index f2a6c1b..4953e72 100644 --- a/web/zebra-spaces.html +++ b/web/zebra-spaces.html @@ -4153,7 +4153,14 @@ function renderRoom(){ * - OTHER rows: host only (room-wide control) * Speakers/cohosts get self-monitor only. Listeners get NO button * at all — they're locked into DJ mode unconditionally for the - * glitch-free playback fox: 'epic, never choppy'. */ + * glitch-free playback fox: 'epic, never choppy'. + * + * Lives in its own `strm` grid column on the TOP row (alongside the + * mic icon), NOT in the .acts-primary stack — putting it there made + * every speaker row grow a third sub-row of mod-actions even for + * non-mods (just-self-monitor self-rows), which fox flagged as + * causing vertical scroll. Single-char glyph keeps the column 1.4rem. */ + let streamEl = null; if (canSpeak(m.role) && m.pubkey){ let mPubHex = ''; try { mPubHex = hex(unb64(m.pubkey)); } catch(_){} @@ -4161,21 +4168,20 @@ function renderRoom(){ const isSelf = (m.uuid === myUUID); const canStreamThisRow = isSelf || myRole === 'host'; if (canStreamThisRow){ - const sb = document.createElement('button'); sb.className = 'small'; const on = streamMode.has(mPubHex); - sb.textContent = on ? '◉ stream' : '○ stream'; + streamEl = document.createElement('button'); + streamEl.className = 'stream-toggle' + (on ? ' on' : ''); + streamEl.textContent = on ? '◉' : '○'; if (isSelf){ - sb.title = on - ? 'monitoring your own DJ stream — click to stop (feedback risk on open speakers!)' + streamEl.title = on + ? 'monitoring your DJ stream — click to stop (feedback risk on open speakers!)' : 'preview what listeners hear of YOUR mic (~2s delay) — headphones recommended'; } else { - sb.title = on - ? 'streaming — click to switch back to live (low-latency WebRTC)' - : 'switch to HTTP Ogg/Opus stream (~2s delay, glitch-free for DJ sets)'; + streamEl.title = on + ? 'streaming — click to switch back to live WebRTC' + : 'switch this speaker to HTTP Ogg/Opus stream (~2s delay, glitch-free)'; } - if (on) sb.classList.add('invert'); - sb.onclick = () => toggleStreamFor(m.uuid, mPubHex); - actsPrimary.appendChild(sb); + streamEl.onclick = () => toggleStreamFor(m.uuid, mPubHex); } } } @@ -4221,7 +4227,13 @@ function renderRoom(){ acts.appendChild(actsPrimary); acts.appendChild(actsRemoval); row.appendChild(badge); row.appendChild(handle); row.appendChild(pub); - row.appendChild(micEl); row.appendChild(meter); row.appendChild(acts); + row.appendChild(micEl); + /* stream toggle (when rendered) goes between mic and meter so it + * lands in the `strm` grid column on the top row. If absent the + * column collapses to its 1.4rem track width (small whitespace + * gap, no row-height change). */ + if (streamEl) row.appendChild(streamEl); + row.appendChild(meter); row.appendChild(acts); wrap.appendChild(row); } /* keep listener UI in sync */ @@ -4697,8 +4709,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');