Three issues fixed together:
1. Mobile horizontal scroll: dynamically-appended <audio> sinks render
their default ~300px control strip on Firefox mobile, pushing the
page wider than the viewport. Hide them globally with
'audio { display: none }' — they're just sinks for the WebRTC track,
no UI required.
2. Don't use literal 50/50 split: page grid is now
'minmax(0, 1fr) minmax(0, 520px)' so the controls cap at 520px and
the timeline soaks the remainder. min-width:0 on both children so
they shrink without forcing an outer scrollbar.
3. Mobile order: on phones the controls were stacked ABOVE the
timeline placeholder; fox wants them first. Use 'order: 1' on
.controls and 'order: 2' on .timeline inside the 800px breakpoint.
4. Host rejoin audio: when a host leaves and rejoins, Pion frequently
reuses the same transceiver on the SFU side, so 'ontrack' doesn't
fire a second time on the listener — but the listener already tore
down the audio element on peer-left. The audio plays into nothing.
Replace the pending-tracks queue with sfuStreamsByPubHex (keyed by
the publisher's pubkey, which is the streamID). The cached
MediaStream survives the host's session uuid changing across
leave/rejoin. On peer-joined / host-promoted / state we look up the
cached stream by pubkey and attach to the fresh audio element.
Also call a.play() after the srcObject swap so the autoplay policy
doesn't quietly drop the resumed playback.