diff --git a/zebra-report/zebra-audio.html b/zebra-report/zebra-audio.html index 4b91560..5d3e2d7 100644 --- a/zebra-report/zebra-audio.html +++ b/zebra-report/zebra-audio.html @@ -51,8 +51,15 @@ .status-line.ok { color: #060; } .status-line.err { color: #b00; } .note { font-size: 0.75rem; color: #555; line-height: 1.5; } - .meter { height: 12px; border: 1px solid #000; background: #fff; position: relative; overflow: hidden; flex: 1; } + .meter { height: 12px; border: 1px solid #000; background: #fff; position: relative; overflow: hidden; } .meter-fill { height: 100%; background: #000; width: 0%; transition: width 0.06s linear; } + /* grid layouts so controls never reflow when a label/state changes */ + .controls { display: grid; grid-template-columns: auto 1fr auto auto; align-items: center; gap: 0.6rem; margin-bottom: 0.75rem; } + #btn-mute { width: 6rem; text-align: center; } /* fixed width: 'mute'/'unmute' never shifts the row */ + .level { display: grid; grid-template-columns: 3rem 1.5rem 1fr; align-items: center; gap: 0.6rem; margin-bottom: 0.6rem; } + .level .who { font-size: 0.8rem; color: #555; } + .mic { width: 18px; height: 18px; } + .mic svg { width: 18px; height: 18px; display: block; } .log { border: 1px solid #000; height: 150px; overflow-y: auto; padding: 0.5rem; font-size: 0.75rem; line-height: 1.5; background: #fafafa; } .log-line { margin-bottom: 0.2rem; word-wrap: break-word; } .log-line .ts { color: #888; } @@ -71,10 +78,10 @@ -
+
idle - +
@@ -91,15 +98,15 @@

levels

-
- you +
+ you +
-
-
- them +
+ them +
-
@@ -219,7 +226,7 @@ async function ensurePC(){ const s = pc.connectionState; setStatus('call: '+s, s==='connected'?'ok':null); $('dot-call').className = 'dot ' + (s==='connected'?'ok':'warn'); - if (s==='connected'){ $('btn-mute').disabled=false; $('btn-hangup').disabled=false; setTimeout(reportPath,1200); setMicLabels(); sendMicState(); } + if (s==='connected'){ $('btn-mute').disabled=false; $('btn-hangup').disabled=false; setTimeout(reportPath,1200); partnerKnown=true; setMicIcons(); sendMicState(); } else if (s==='failed'||s==='disconnected'||s==='closed'){ setPath('path: —', null); } }; return pc; @@ -273,7 +280,7 @@ async function startCall(){ const code = $('rdv-code').value.trim(); if (!code){ setStatus('enter a rendezvous code first','err'); return; } if (relayWS){ setStatus('already in a call — hang up first', null); return; } - try { await getMic(); meterFor(micStream,'meter-mic'); } + try { await getMic(); meterFor(micStream,'meter-mic'); setMicIcons(); } catch(e){ setStatus('microphone blocked: '+e.message,'err'); return; } await refreshTurnCred(); relayKey = await deriveSignalKey(code); @@ -300,7 +307,7 @@ async function startCall(){ $('remote-audio').srcObject=null; $('meter-rem').style.width='0%'; $('dot-call').className='dot warn'; setPath('path: —', null); $('btn-mute').disabled=true; muted=false; $('btn-mute').textContent='mute'; $('btn-mute').className=''; - setPartnerMic(false); setMicLabels(); + partnerKnown=false; partnerMuted=false; setMicIcons(); setStatus('partner left — waiting for them to return', null); } else if (m.type==='sdp'){ try { await onSDP(m.kind, await aesDecrypt(relayKey, unb64(m.data))); } catch(e){ logLine('err','sdp failed (codes must match): '+e.message); } } @@ -315,15 +322,17 @@ function hangup(){ $('btn-mute').disabled=true; $('btn-hangup').disabled=true; $('btn-call').disabled=false; $('dot-call').className='dot warn'; setStatus('idle', null); setPath('path: —', null); $('meter-rem').style.width='0%'; - muted=false; partnerMuted=false; $('btn-mute').textContent='mute'; $('btn-mute').className=''; setMicLabels(); + muted=false; partnerMuted=false; partnerKnown=false; $('btn-mute').textContent='mute'; $('btn-mute').className=''; setMicIcons(); logLine('', 'call ended'); } -let muted=false, partnerMuted=false; -function setMicLabels(){ - $('you-state').textContent = muted ? 'muted' : ''; - $('them-state').textContent = partnerMuted ? 'muted' : ''; +let muted=false, partnerMuted=false, partnerKnown=false; +const MIC_ON = ''; +const MIC_OFF = ''; +function setMicIcons(){ + $('you-mic').innerHTML = micStream ? (muted ? MIC_OFF : MIC_ON) : ''; + $('them-mic').innerHTML = partnerKnown ? (partnerMuted ? MIC_OFF : MIC_ON) : ''; } -function setPartnerMic(m){ partnerMuted=m; setMicLabels(); logLine('', 'partner '+(m?'muted':'unmuted')); } +function setPartnerMic(m){ partnerMuted=m; partnerKnown=true; setMicIcons(); logLine('', 'partner '+(m?'muted':'unmuted')); } /* advertise our mic state to the partner over the rendezvous channel, encrypted * with the room code so the relay never learns it */ async function sendMicState(){ @@ -336,7 +345,7 @@ function toggleMute(){ micStream.getAudioTracks().forEach(t=>t.enabled=!muted); $('btn-mute').textContent = muted?'unmute':'mute'; $('btn-mute').className = muted?'invert':''; - setMicLabels(); + setMicIcons(); sendMicState(); }