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');