From e6e81e7823166972011aafa7d1221cb1085087f1 Mon Sep 17 00:00:00 2001 From: G-Fourteen Date: Fri, 31 Oct 2025 19:09:13 -0600 Subject: [PATCH] Improve mute toggle UX and voice activity visuals --- app.js | 22 +++++++++++++++------- style.css | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 8 deletions(-) diff --git a/app.js b/app.js index 006aeb9..80c6000 100644 --- a/app.js +++ b/app.js @@ -321,7 +321,7 @@ function updateMuteIndicator() { muteIndicator.dataset.state = 'muted'; muteIndicator.setAttribute('aria-label', 'Microphone muted. Tap to enable listening.'); } else { - indicatorText && (indicatorText.textContent = 'Listening… tap to mute'); + indicatorText && (indicatorText.textContent = 'Tap or click anywhere to mute'); muteIndicator.dataset.state = 'listening'; muteIndicator.setAttribute('aria-label', 'Microphone active. Tap to mute.'); } @@ -388,17 +388,25 @@ function handleMuteToggle(event) { muteIndicator?.addEventListener('click', handleMuteToggle); -document.addEventListener('click', () => { - if (isMuted) { - attemptUnmute(); +document.addEventListener('click', (event) => { + if (muteIndicator?.contains(event.target)) { + return; } + + handleMuteToggle(event); }); document.addEventListener('keydown', (event) => { - if ((event.key === 'Enter' || event.key === ' ') && isMuted) { - event.preventDefault(); - attemptUnmute(); + if (event.key !== 'Enter' && event.key !== ' ') { + return; } + + if (event.target && ['INPUT', 'TEXTAREA'].includes(event.target.tagName)) { + return; + } + + event.preventDefault(); + handleMuteToggle(event); }); let speakingFallbackTimeout = null; diff --git a/style.css b/style.css index aa3e919..f20f331 100644 --- a/style.css +++ b/style.css @@ -129,10 +129,59 @@ body { justify-content: center; background: rgba(12, 14, 24, 0.68); backdrop-filter: blur(12px); - overflow: hidden; + overflow: visible; + isolation: isolate; transition: border-color 0.4s ease, box-shadow 0.4s ease, transform 0.4s ease; } +.voice-circle::before { + content: ""; + position: absolute; + inset: -18%; + border-radius: 50%; + opacity: 0; + transform: scale(0.92); + transition: opacity 0.4s ease, transform 0.4s ease; + z-index: -1; + filter: blur(12px); + pointer-events: none; +} + +.voice-circle.ai::before { + background: radial-gradient( + circle, + rgba(124, 92, 255, 0.55) 0%, + rgba(124, 92, 255, 0.18) 42%, + rgba(124, 92, 255, 0) 70% + ); +} + +.voice-circle.user::before { + background: radial-gradient( + circle, + rgba(67, 217, 189, 0.55) 0%, + rgba(67, 217, 189, 0.2) 42%, + rgba(67, 217, 189, 0) 70% + ); +} + +.voice-circle.is-speaking::before, +.voice-circle.is-listening::before { + opacity: 1; + transform: scale(1.08); +} + +.voice-circle.is-error::before { + opacity: 1; + transform: scale(1.02); + background: radial-gradient( + circle, + rgba(255, 99, 132, 0.65) 0%, + rgba(255, 99, 132, 0.22) 40%, + rgba(255, 99, 132, 0) 70% + ); +} + .voice-circle::after { content: ""; position: absolute;