Live: windows open as people go live and close when they stop. Click a window to hear it; the speaker button pins audio on; the slider is per window.
+ Live page: windows, groups, names
+
+
Layout. Windows arrange themselves into an even grid whenever someone goes live or stops. Drag a tab to move a window, drag the bars between windows to resize, drop a tab onto another window to stack them as tabs. Arrange (top bar) snaps everything back to the grid.
+
Audio. Sound follows the window you click. The speaker button pins a window's audio on even when you click elsewhere; Mute all silences everything until you click a window; Follow focus clears pins.
+
Rename a stream. Double-click its tab, type a name, Enter. Escape cancels; an empty name goes back to user · title. Names stay in your browser and come back the next time that streamer uses the same title.
+
Name a group. Every tab bar starts with a + name slot. Double-click it, type, Enter. Group names last until you reload the page.
+
One window big. The ⛶ button at the right of a tab bar maximises that window; click it again to restore. Arranging (a stream joining or leaving, or the Arrange button) restores it too.
+
Timecode band. Streams with the timecode band on show it cropped off automatically; recordings never contain it.
+
+
diff --git a/live/src/App.jsx b/live/src/App.jsx
index c8e2d9f..f7e8cf2 100644
--- a/live/src/App.jsx
+++ b/live/src/App.jsx
@@ -6,6 +6,7 @@ import { audio } from './useAudio.js'
import GoLive from './GoLive.jsx'
import Tab from './Tab.jsx'
import GroupLabel from './GroupLabel.jsx'
+import GroupActions from './GroupActions.jsx'
import { streamLabel } from './names.js'
const components = { player: Player }
@@ -18,6 +19,7 @@ const POLL_MS = 3000
export function arrange(api) {
const panels = [...api.panels].sort((a, b) => ((a.params?.since || '').localeCompare(b.params?.since || '')) || a.id.localeCompare(b.id))
const n = panels.length; if (n < 2) return
+ if (api.hasMaximizedGroup?.()) api.exitMaximizedGroup?.() // a maximised window would hide the new grid
const cols = Math.ceil(Math.sqrt(n))
for (let i = 1; i < n; i++) {
const r = Math.floor(i / cols), c = i % cols
@@ -92,7 +94,7 @@ export default function App() {
)}