hermes(voice): keep one consistent ellipsis on conversation status labels
The overlay status caption already carries a static ellipsis (e.g. 'Thinking…'), and after ~2.5s of silent thinking the 'working' affordance added an animated dots ::after on top of it — so the label intermittently rendered as 'Thinking……' only on longer turns. Drop the animated text dots and keep the orb-halo shimmer as the liveness cue, so the ellipsis stays a single, consistent static one. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
This commit is contained in:
parent
9f49a1ba2c
commit
5b1f832072
@ -809,38 +809,21 @@
|
|||||||
filter: saturate(0.35) brightness(0.8);
|
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
|
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
|
reads as alive. A slow orb shimmer only — the state caption keeps its single
|
||||||
caption — cheap, CSS-only, and fully suppressed under reduced motion by the
|
static ellipsis (e.g. "Thinking…") and is never trailed by animated dots, so
|
||||||
blanket rule below. */
|
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 {
|
.voice-conversation.is-awaiting .voice-conversation-orb-halo {
|
||||||
animation: voice-conversation-await-shimmer 2.6s ease-in-out infinite;
|
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 {
|
@keyframes voice-conversation-await-shimmer {
|
||||||
0%, 100% { opacity: 0.6; filter: blur(10px); }
|
0%, 100% { opacity: 0.6; filter: blur(10px); }
|
||||||
50% { opacity: 0.92; filter: blur(13px); }
|
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 {
|
@keyframes voice-conversation-breathe {
|
||||||
0%, 100% { opacity: 0.55; transform: scale(calc(0.96 + (var(--conversation-level) * 0.3))); }
|
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))); }
|
50% { opacity: 0.85; transform: scale(calc(1.05 + (var(--conversation-level) * 0.3))); }
|
||||||
|
|||||||
@ -629,6 +629,13 @@ def test_conversation_thinking_working_affordance_contract():
|
|||||||
# The affordance disarms the instant any reply text arrives.
|
# The affordance disarms the instant any reply text arrives.
|
||||||
assert "if(text&&String(text).trim()) clearAwaitingAffordance();" in source
|
assert "if(text&&String(text).trim()) clearAwaitingAffordance();" in source
|
||||||
assert ".voice-conversation.is-awaiting" in css
|
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.
|
# Reduced motion suppresses every overlay animation, including this one.
|
||||||
rm_block = css.split("@media (prefers-reduced-motion: reduce)", 1)[1]
|
rm_block = css.split("@media (prefers-reduced-motion: reduce)", 1)[1]
|
||||||
assert "animation: none !important" in rm_block
|
assert "animation: none !important" in rm_block
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user