diff --git a/dockerfiles/hermes-webui-atlas-voice.css b/dockerfiles/hermes-webui-atlas-voice.css index 0612733f..8e1d1ec3 100644 --- a/dockerfiles/hermes-webui-atlas-voice.css +++ b/dockerfiles/hermes-webui-atlas-voice.css @@ -809,38 +809,21 @@ filter: saturate(0.35) brightness(0.8); } -/* FIX 4: "working…" affordance. Armed by atlas-voice.js (.is-awaiting) only +/* FIX 4: "working" affordance. Armed by atlas-voice.js (.is-awaiting) only after the model has been Thinking a beat with no reply text yet, so dead time - reads as alive. A slow orb shimmer plus animated dots trailing the state - caption — cheap, CSS-only, and fully suppressed under reduced motion by the - blanket rule below. */ + reads as alive. A slow orb shimmer only — the state caption keeps its single + static ellipsis (e.g. "Thinking…") and is never trailed by animated dots, so + the ellipsis stays consistent instead of doubling up. Cheap, CSS-only, and + fully suppressed under reduced motion by the blanket rule below. */ .voice-conversation.is-awaiting .voice-conversation-orb-halo { animation: voice-conversation-await-shimmer 2.6s ease-in-out infinite; } -.voice-conversation.is-awaiting .voice-conversation-state::after { - content: ""; - display: inline-block; - width: 1.4em; - margin-left: 0.15em; - text-align: left; - vertical-align: bottom; - animation: voice-conversation-await-dots 1.4s steps(4, end) infinite; -} - @keyframes voice-conversation-await-shimmer { 0%, 100% { opacity: 0.6; filter: blur(10px); } 50% { opacity: 0.92; filter: blur(13px); } } -@keyframes voice-conversation-await-dots { - 0% { content: ""; } - 25% { content: "·"; } - 50% { content: "··"; } - 75% { content: "···"; } - 100% { content: ""; } -} - @keyframes voice-conversation-breathe { 0%, 100% { opacity: 0.55; transform: scale(calc(0.96 + (var(--conversation-level) * 0.3))); } 50% { opacity: 0.85; transform: scale(calc(1.05 + (var(--conversation-level) * 0.3))); } diff --git a/testing/tests/test_hermes_voice_capture_continuity.py b/testing/tests/test_hermes_voice_capture_continuity.py index 283bb66d..6300c553 100644 --- a/testing/tests/test_hermes_voice_capture_continuity.py +++ b/testing/tests/test_hermes_voice_capture_continuity.py @@ -629,6 +629,13 @@ def test_conversation_thinking_working_affordance_contract(): # The affordance disarms the instant any reply text arrives. assert "if(text&&String(text).trim()) clearAwaitingAffordance();" in source assert ".voice-conversation.is-awaiting" in css + # The affordance is an orb shimmer only: the state caption keeps its single + # static ellipsis and is never trailed by animated dots, so "Thinking…" can + # never render as "Thinking……". (Consistency fix.) + assert "voice-conversation-await-shimmer" in css + assert "voice-conversation-await-dots" not in css + assert ".voice-conversation-state::after" not in css + assert "thinking:'Thinking…'" in source and "transcribing:'Transcribing…'" in source # Reduced motion suppresses every overlay animation, including this one. rm_block = css.split("@media (prefers-reduced-motion: reduce)", 1)[1] assert "animation: none !important" in rm_block