2026-08-20 23:13:51 +00:00
|
|
|
/* Private hands-free conversation instrument for the Atlas voice bridge. */
|
|
|
|
|
.voice-mode-bar {
|
|
|
|
|
--voice-accent: 72, 207, 204;
|
|
|
|
|
--voice-accent-secondary: 76, 164, 205;
|
|
|
|
|
position: relative;
|
|
|
|
|
isolation: isolate;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
min-height: 106px;
|
|
|
|
|
padding: 9px 12px 10px;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
border-bottom: 1px solid rgba(174, 192, 218, 0.1);
|
|
|
|
|
background:
|
|
|
|
|
radial-gradient(circle at 50% 38%, rgba(var(--voice-accent), 0.075), transparent 46%),
|
|
|
|
|
linear-gradient(180deg, rgba(255, 255, 255, 0.025), rgba(4, 8, 18, 0.035));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-mode-bar::before {
|
|
|
|
|
content: "";
|
|
|
|
|
position: absolute;
|
|
|
|
|
z-index: -1;
|
|
|
|
|
top: 0;
|
|
|
|
|
left: 18%;
|
|
|
|
|
right: 18%;
|
|
|
|
|
height: 1px;
|
|
|
|
|
background: linear-gradient(90deg, transparent, rgba(233, 244, 255, 0.16), transparent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:root:not(.dark) .voice-mode-bar {
|
|
|
|
|
border-bottom-color: rgba(38, 55, 78, 0.1);
|
|
|
|
|
background:
|
|
|
|
|
radial-gradient(circle at 50% 38%, rgba(var(--voice-accent), 0.09), transparent 48%),
|
|
|
|
|
linear-gradient(180deg, rgba(255, 255, 255, 0.72), rgba(226, 232, 240, 0.12));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-mode-indicator {
|
|
|
|
|
--voice-ripple-scale: 1.035;
|
|
|
|
|
--voice-ripple-opacity: 0.3;
|
|
|
|
|
position: relative;
|
|
|
|
|
width: 70px;
|
|
|
|
|
height: 70px;
|
|
|
|
|
flex: 0 0 70px;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
transform: translateZ(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-mode-indicator.listening {
|
|
|
|
|
--voice-accent: 69, 218, 207;
|
|
|
|
|
--voice-accent-secondary: 73, 169, 209;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-mode-indicator.transcribing {
|
|
|
|
|
--voice-accent: 73, 193, 218;
|
|
|
|
|
--voice-accent-secondary: 93, 129, 224;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-mode-indicator.thinking {
|
|
|
|
|
--voice-accent: 135, 111, 244;
|
|
|
|
|
--voice-accent-secondary: 81, 62, 190;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-mode-indicator.speaking {
|
|
|
|
|
--voice-accent: 249, 169, 105;
|
|
|
|
|
--voice-accent-secondary: 236, 112, 137;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-mode-indicator.error {
|
|
|
|
|
--voice-accent: 235, 135, 88;
|
|
|
|
|
--voice-accent-secondary: 198, 74, 78;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-instrument-halo,
|
|
|
|
|
.voice-instrument-ripple,
|
|
|
|
|
.voice-instrument-orbit,
|
|
|
|
|
.voice-instrument-core {
|
|
|
|
|
position: absolute;
|
|
|
|
|
display: block;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-instrument-halo {
|
|
|
|
|
inset: 2px;
|
|
|
|
|
opacity: 0;
|
|
|
|
|
background: radial-gradient(circle, rgba(var(--voice-accent), 0.2), rgba(var(--voice-accent), 0.05) 43%, transparent 70%);
|
|
|
|
|
filter: blur(4px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-instrument-ripple {
|
|
|
|
|
inset: 3px;
|
|
|
|
|
opacity: 0;
|
|
|
|
|
border: 1px solid rgba(var(--voice-accent), 0.56);
|
|
|
|
|
box-shadow: 0 0 12px rgba(var(--voice-accent), 0.13);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-instrument-orbit {
|
|
|
|
|
inset: 2px;
|
|
|
|
|
opacity: 0;
|
|
|
|
|
border: 1px solid rgba(var(--voice-accent), 0.18);
|
|
|
|
|
box-shadow:
|
|
|
|
|
inset 0 0 8px rgba(var(--voice-accent), 0.06),
|
|
|
|
|
0 0 11px rgba(var(--voice-accent), 0.07);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-instrument-orbit::before {
|
|
|
|
|
content: "";
|
|
|
|
|
position: absolute;
|
|
|
|
|
inset: -1px;
|
|
|
|
|
border-radius: inherit;
|
|
|
|
|
border-top: 1.5px solid rgba(var(--voice-accent), 0.88);
|
|
|
|
|
border-right: 1px solid rgba(var(--voice-accent-secondary), 0.35);
|
|
|
|
|
border-bottom: 1px solid transparent;
|
|
|
|
|
border-left: 1px solid transparent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-instrument-orbit::after {
|
|
|
|
|
content: "";
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: -2px;
|
|
|
|
|
left: 50%;
|
|
|
|
|
width: 5px;
|
|
|
|
|
height: 5px;
|
|
|
|
|
margin-left: -2.5px;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
background: rgb(var(--voice-accent));
|
|
|
|
|
box-shadow:
|
|
|
|
|
0 0 5px rgba(var(--voice-accent), 0.92),
|
|
|
|
|
0 0 12px rgba(var(--voice-accent), 0.44);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-instrument-core {
|
|
|
|
|
inset: 12px;
|
|
|
|
|
display: grid;
|
|
|
|
|
place-items: center;
|
|
|
|
|
color: rgba(242, 250, 255, 0.94);
|
|
|
|
|
border: 1px solid rgba(238, 247, 255, 0.2);
|
|
|
|
|
background:
|
|
|
|
|
radial-gradient(circle at 38% 29%, rgba(255, 255, 255, 0.26), transparent 24%),
|
|
|
|
|
radial-gradient(circle at 50% 66%, rgba(var(--voice-accent), 0.36), transparent 64%),
|
|
|
|
|
linear-gradient(145deg, rgba(36, 45, 61, 0.96), rgba(10, 15, 27, 0.98));
|
|
|
|
|
box-shadow:
|
|
|
|
|
inset 0 1px 1px rgba(255, 255, 255, 0.22),
|
|
|
|
|
inset 0 -9px 18px rgba(0, 0, 0, 0.24),
|
|
|
|
|
0 4px 13px rgba(0, 0, 0, 0.25),
|
|
|
|
|
0 0 17px rgba(var(--voice-accent), 0.17);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:root:not(.dark) .voice-instrument-core {
|
|
|
|
|
color: rgba(31, 48, 66, 0.9);
|
|
|
|
|
border-color: rgba(255, 255, 255, 0.82);
|
|
|
|
|
background:
|
|
|
|
|
radial-gradient(circle at 38% 29%, rgba(255, 255, 255, 0.96), transparent 27%),
|
|
|
|
|
radial-gradient(circle at 50% 66%, rgba(var(--voice-accent), 0.2), transparent 65%),
|
|
|
|
|
linear-gradient(145deg, rgba(248, 250, 251, 0.98), rgba(216, 225, 231, 0.96));
|
|
|
|
|
box-shadow:
|
|
|
|
|
inset 0 1px 1px rgba(255, 255, 255, 0.94),
|
|
|
|
|
inset 0 -8px 17px rgba(64, 84, 100, 0.1),
|
|
|
|
|
0 4px 12px rgba(35, 53, 72, 0.15),
|
|
|
|
|
0 0 15px rgba(var(--voice-accent), 0.13);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-instrument-symbol {
|
|
|
|
|
display: block;
|
|
|
|
|
width: 22px;
|
|
|
|
|
height: 22px;
|
|
|
|
|
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.32));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-instrument-symbol svg {
|
|
|
|
|
display: block;
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-symbol {
|
|
|
|
|
display: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-mode-indicator.listening .voice-symbol-listening,
|
|
|
|
|
.voice-mode-indicator.transcribing .voice-symbol-transcribing,
|
|
|
|
|
.voice-mode-indicator.thinking .voice-symbol-thinking,
|
|
|
|
|
.voice-mode-indicator.speaking .voice-symbol-speaking,
|
|
|
|
|
.voice-mode-indicator.error .voice-symbol-error {
|
|
|
|
|
display: inline;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-mode-indicator.listening .voice-instrument-halo {
|
|
|
|
|
opacity: 0.62;
|
|
|
|
|
animation: voice-instrument-breathe 3.8s ease-in-out infinite;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-mode-indicator.listening .voice-instrument-ripple {
|
|
|
|
|
opacity: var(--voice-ripple-opacity);
|
|
|
|
|
transform: scale(var(--voice-ripple-scale));
|
|
|
|
|
transition: transform 140ms ease-out, opacity 160ms ease-out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-mode-indicator.transcribing .voice-instrument-orbit {
|
|
|
|
|
opacity: 0.92;
|
|
|
|
|
animation: voice-instrument-orbit 3.4s cubic-bezier(0.58, 0.12, 0.42, 0.88) infinite;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-mode-indicator.thinking .voice-instrument-orbit {
|
|
|
|
|
opacity: 0.96;
|
|
|
|
|
animation: voice-instrument-orbit 4.6s cubic-bezier(0.58, 0.12, 0.42, 0.88) infinite;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-mode-indicator.thinking .voice-instrument-core {
|
|
|
|
|
animation: voice-instrument-core-breathe 5.7s ease-in-out infinite;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-mode-indicator.speaking .voice-instrument-orbit {
|
|
|
|
|
opacity: 0.86;
|
|
|
|
|
transform: rotate(38deg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-mode-indicator.speaking.is-playing .voice-instrument-halo {
|
|
|
|
|
opacity: 0.66;
|
|
|
|
|
animation: voice-instrument-speaking-pulse 2.15s ease-out infinite;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-mode-indicator.error .voice-instrument-orbit {
|
|
|
|
|
opacity: 0.9;
|
|
|
|
|
transform: rotate(-32deg);
|
|
|
|
|
border-color: rgba(var(--voice-accent), 0.44);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-mode-indicator.error .voice-instrument-core {
|
|
|
|
|
color: rgba(255, 232, 219, 0.95);
|
|
|
|
|
box-shadow:
|
|
|
|
|
inset 0 1px 1px rgba(255, 255, 255, 0.2),
|
|
|
|
|
inset 0 -9px 18px rgba(0, 0, 0, 0.24),
|
|
|
|
|
0 4px 13px rgba(0, 0, 0, 0.25),
|
|
|
|
|
0 0 12px rgba(var(--voice-accent), 0.17);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-mode-label {
|
|
|
|
|
position: relative;
|
|
|
|
|
z-index: 1;
|
|
|
|
|
min-height: 16px;
|
|
|
|
|
max-width: min(100%, 34rem);
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
color: var(--text, #f4f6fb);
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
font-weight: 650;
|
|
|
|
|
line-height: 1.35;
|
|
|
|
|
letter-spacing: 0.018em;
|
|
|
|
|
text-align: center;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.28);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:root:not(.dark) .voice-mode-label {
|
|
|
|
|
color: #263648;
|
|
|
|
|
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.8);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes voice-instrument-breathe {
|
|
|
|
|
0%, 100% { opacity: 0.38; transform: scale(0.94); }
|
|
|
|
|
50% { opacity: 0.72; transform: scale(1.06); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes voice-instrument-orbit {
|
|
|
|
|
0% { transform: rotate(0deg); }
|
|
|
|
|
50% { transform: rotate(177deg); }
|
|
|
|
|
100% { transform: rotate(360deg); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes voice-instrument-core-breathe {
|
|
|
|
|
0%, 100% { filter: saturate(0.96) brightness(0.98); }
|
|
|
|
|
50% { filter: saturate(1.08) brightness(1.06); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes voice-instrument-speaking-pulse {
|
|
|
|
|
0% { opacity: 0.58; transform: scale(0.88); }
|
|
|
|
|
58% { opacity: 0.22; transform: scale(1.12); }
|
|
|
|
|
100% { opacity: 0; transform: scale(1.2); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 640px) {
|
|
|
|
|
.voice-mode-bar {
|
|
|
|
|
min-height: 94px;
|
|
|
|
|
padding: 7px 10px 8px;
|
|
|
|
|
gap: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-mode-indicator {
|
|
|
|
|
width: 62px;
|
|
|
|
|
height: 62px;
|
|
|
|
|
flex-basis: 62px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-instrument-core {
|
|
|
|
|
inset: 11px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-instrument-symbol {
|
|
|
|
|
width: 20px;
|
|
|
|
|
height: 20px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
|
|
|
.voice-mode-bar *,
|
|
|
|
|
.voice-mode-bar *::before,
|
|
|
|
|
.voice-mode-bar *::after {
|
|
|
|
|
animation: none !important;
|
|
|
|
|
transition: none !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-mode-indicator.listening .voice-instrument-halo {
|
|
|
|
|
opacity: 0.45;
|
|
|
|
|
transform: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-mode-indicator.listening .voice-instrument-ripple {
|
|
|
|
|
opacity: 0.34;
|
|
|
|
|
transform: scale(1.035);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-mode-indicator.transcribing .voice-instrument-orbit,
|
|
|
|
|
.voice-mode-indicator.thinking .voice-instrument-orbit {
|
|
|
|
|
transform: rotate(24deg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-mode-indicator.speaking.is-playing .voice-instrument-halo {
|
|
|
|
|
opacity: 0.42;
|
|
|
|
|
transform: scale(1.03);
|
|
|
|
|
}
|
|
|
|
|
}
|
hermes(voice): fix first-word clipping, always-stitch, panel glow, conversation mode
Round 2 from live testing:
- Clipping root cause: the endpointer committed on any 1.1s pause, so a
thinking pause after a sentence opener sent one word; a speculative
Whisper pass over that fragment then stalled the real commit ~3.5s on
the Jetson. Young utterances now hold a 1.8s endpoint until 1.2s of
speech accrues, speculative decode waits for 700ms of speech, resume
is unconditional after any 650ms gap (server-frozen snapshots can
never reach commit), and the noise floor is capped so playback echo
cannot deafen onset. Deterministic capture-continuity probe added.
- Stitching now fires for any barge-cancelled send within 20s,
regardless of partial assistant output.
- The workspace-drawer dark rectangle was our own HUX chrome resolving
undefined theme tokens (--bg-primary) to a flat box; bootstrap.css
bridges the real theme tokens and drops a compositor-hazard
backdrop-filter.
- Conversation mode: full-viewport hands-free overlay with an energy-
driven orb (mic RMS in, TTS activity out), state caption, live
transcript/reply captions, mute and exit controls, focus trap,
Escape, reduced-motion support. Voice lane 253 tests green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
2026-08-24 11:21:35 -03:00
|
|
|
|
|
|
|
|
/* ── Conversation mode overlay ─────────────────────────────────────────── */
|
|
|
|
|
/* Full-viewport hands-free conversation surface. Created lazily by
|
|
|
|
|
atlas-voice.js on activation and removed on deactivate. The orb breathes in
|
|
|
|
|
idle listening, follows live microphone energy through
|
|
|
|
|
--conversation-level, and pulses while Hermes speaks (.is-playing, driven
|
|
|
|
|
from TTS playback activity). State accents reuse the indicator palette. */
|
|
|
|
|
.voice-conversation {
|
|
|
|
|
--voice-accent: 69, 218, 207;
|
|
|
|
|
--voice-accent-secondary: 73, 169, 209;
|
|
|
|
|
--conversation-level: 0;
|
|
|
|
|
position: fixed;
|
|
|
|
|
inset: 0;
|
|
|
|
|
z-index: 420;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
gap: 24px;
|
|
|
|
|
padding: 34px 22px calc(28px + env(safe-area-inset-bottom, 0px));
|
|
|
|
|
background:
|
|
|
|
|
radial-gradient(circle at 50% 30%, rgba(var(--voice-accent), 0.11), transparent 52%),
|
|
|
|
|
radial-gradient(circle at 22% 92%, rgba(157, 142, 224, 0.07), transparent 44%),
|
|
|
|
|
linear-gradient(160deg, #0b1120, #070a12 55%, #081018);
|
|
|
|
|
color: rgba(240, 247, 255, 0.94);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation[data-voice-state="transcribing"] {
|
|
|
|
|
--voice-accent: 73, 193, 218;
|
|
|
|
|
--voice-accent-secondary: 93, 129, 224;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation[data-voice-state="thinking"] {
|
|
|
|
|
--voice-accent: 135, 111, 244;
|
|
|
|
|
--voice-accent-secondary: 81, 62, 190;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation[data-voice-state="speaking"] {
|
|
|
|
|
--voice-accent: 249, 169, 105;
|
|
|
|
|
--voice-accent-secondary: 236, 112, 137;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation[data-voice-state="error"] {
|
|
|
|
|
--voice-accent: 235, 135, 88;
|
|
|
|
|
--voice-accent-secondary: 198, 74, 78;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-orb {
|
|
|
|
|
position: relative;
|
|
|
|
|
width: min(46vmin, 220px);
|
|
|
|
|
height: min(46vmin, 220px);
|
|
|
|
|
flex: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-orb-halo,
|
|
|
|
|
.voice-conversation-orb-core,
|
|
|
|
|
.voice-conversation-orb-ring {
|
|
|
|
|
position: absolute;
|
|
|
|
|
inset: 0;
|
|
|
|
|
display: block;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-orb-halo {
|
|
|
|
|
background: radial-gradient(circle, rgba(var(--voice-accent), 0.34), rgba(var(--voice-accent-secondary), 0.1) 52%, transparent 74%);
|
|
|
|
|
filter: blur(10px);
|
|
|
|
|
opacity: 0.75;
|
|
|
|
|
transform: scale(calc(1 + (var(--conversation-level) * 0.3)));
|
|
|
|
|
transition: transform 130ms ease-out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-orb-core {
|
|
|
|
|
inset: 12%;
|
|
|
|
|
background:
|
|
|
|
|
radial-gradient(circle at 36% 28%, rgba(255, 255, 255, 0.34), transparent 34%),
|
|
|
|
|
radial-gradient(circle at 52% 68%, rgba(var(--voice-accent), 0.62), rgba(var(--voice-accent-secondary), 0.4) 62%, transparent 86%),
|
|
|
|
|
linear-gradient(150deg, rgba(34, 44, 63, 0.95), rgba(9, 13, 24, 0.98));
|
|
|
|
|
box-shadow:
|
|
|
|
|
inset 0 1px 2px rgba(255, 255, 255, 0.28),
|
|
|
|
|
0 10px 42px rgba(0, 0, 0, 0.45),
|
|
|
|
|
0 0 44px rgba(var(--voice-accent), 0.3);
|
|
|
|
|
transform: scale(calc(1 + (var(--conversation-level) * 0.14)));
|
|
|
|
|
transition: transform 110ms ease-out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-orb-ring {
|
|
|
|
|
inset: 3%;
|
|
|
|
|
border: 1.5px solid rgba(var(--voice-accent), 0.4);
|
|
|
|
|
box-shadow: 0 0 22px rgba(var(--voice-accent), 0.16);
|
|
|
|
|
opacity: 0.85;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-24 20:24:31 -03:00
|
|
|
/* FIX 1: the Hermes CHARACTER watermark inside the orb. A pre-processed data
|
|
|
|
|
URI — the 512px character art, feathered to a circle so its edges fade to
|
|
|
|
|
transparent (the orb appears to CROP it, with no square box) and luminance-
|
|
|
|
|
weighted to a warm near-white so the facial features read as light. Painted
|
|
|
|
|
with mix-blend-mode: screen so the glyph's darks drop away and only the light
|
|
|
|
|
face features glow over the dark orb. It is a static, centred, inset watermark
|
|
|
|
|
that scales with the orb (inset-positioned), carries no animation of its own
|
|
|
|
|
(reduced-motion safe) and stays subtle across every state tint. Inlined as a
|
|
|
|
|
data URI, so there is no separate static asset to serve. */
|
2026-08-24 15:46:09 -03:00
|
|
|
.voice-conversation-orb-mark {
|
|
|
|
|
position: absolute;
|
2026-08-24 20:24:31 -03:00
|
|
|
inset: 16%;
|
2026-08-24 15:46:09 -03:00
|
|
|
pointer-events: none;
|
2026-08-24 20:24:31 -03:00
|
|
|
background-image: url("data:image/png;base64,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
|
hermes(voice): workspace nav home, character orb, conversation rename, voice-lang fix
Final conversation-mode polish from mobile testing:
- The Workspace toggle now sits with the chat/Telegram nav at every
width: nav.rail on desktop, the top app titlebar on mobile. The
floating pill that pushed the mobile composer's control row (and the
conversation-mode button) off screen is gone - a fallback exists only
for headless DOMs and is pinned to a top corner, never over the
composer.
- The conversation orb watermark is now the Hermes character avatar
(static/hermes-agent-192.png) instead of the caduceus staff.
- User-facing 'hands-free' copy renamed to 'Conversation mode'.
- Wrong-voice fix: strongReplyLanguage flagged Spanish on a single
accented char, so an English reply naming European cities (Zürich,
Málaga) overrode the correct English STT detection and was spoken by
the Spanish voice. Detection now requires density (Cyrillic >=4 at
>=50%, or inverted punctuation / >=2 accents corroborated by Spanish
stopwords); plain English always speaks English, forced language wins,
accent-free Spanish still routes via trusted STT. 294 voice tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
2026-08-24 19:22:20 -03:00
|
|
|
background-position: center;
|
|
|
|
|
background-repeat: no-repeat;
|
|
|
|
|
background-size: contain;
|
2026-08-24 20:24:31 -03:00
|
|
|
mix-blend-mode: screen;
|
|
|
|
|
opacity: 0.6;
|
hermes(voice): workspace nav home, character orb, conversation rename, voice-lang fix
Final conversation-mode polish from mobile testing:
- The Workspace toggle now sits with the chat/Telegram nav at every
width: nav.rail on desktop, the top app titlebar on mobile. The
floating pill that pushed the mobile composer's control row (and the
conversation-mode button) off screen is gone - a fallback exists only
for headless DOMs and is pinned to a top corner, never over the
composer.
- The conversation orb watermark is now the Hermes character avatar
(static/hermes-agent-192.png) instead of the caduceus staff.
- User-facing 'hands-free' copy renamed to 'Conversation mode'.
- Wrong-voice fix: strongReplyLanguage flagged Spanish on a single
accented char, so an English reply naming European cities (Zürich,
Málaga) overrode the correct English STT detection and was spoken by
the Spanish voice. Detection now requires density (Cyrillic >=4 at
>=50%, or inverted punctuation / >=2 accents corroborated by Spanish
stopwords); plain English always speaks English, forced language wins,
accent-free Spanish still routes via trusted STT. 294 voice tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
2026-08-24 19:22:20 -03:00
|
|
|
transition: opacity 200ms ease-out;
|
2026-08-24 15:46:09 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Speaking/thinking lift the watermark a touch so it feels alive with the turn,
|
2026-08-24 20:24:31 -03:00
|
|
|
still far below any level that would mask the energy layers. */
|
2026-08-24 15:46:09 -03:00
|
|
|
.voice-conversation[data-voice-state="speaking"] .voice-conversation-orb-mark,
|
|
|
|
|
.voice-conversation[data-voice-state="thinking"] .voice-conversation-orb-mark {
|
2026-08-24 20:24:31 -03:00
|
|
|
opacity: 0.72;
|
2026-08-24 15:46:09 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation.is-muted .voice-conversation-orb-mark {
|
2026-08-24 20:24:31 -03:00
|
|
|
opacity: 0.4;
|
2026-08-24 15:46:09 -03:00
|
|
|
}
|
|
|
|
|
|
hermes(voice): fix first-word clipping, always-stitch, panel glow, conversation mode
Round 2 from live testing:
- Clipping root cause: the endpointer committed on any 1.1s pause, so a
thinking pause after a sentence opener sent one word; a speculative
Whisper pass over that fragment then stalled the real commit ~3.5s on
the Jetson. Young utterances now hold a 1.8s endpoint until 1.2s of
speech accrues, speculative decode waits for 700ms of speech, resume
is unconditional after any 650ms gap (server-frozen snapshots can
never reach commit), and the noise floor is capped so playback echo
cannot deafen onset. Deterministic capture-continuity probe added.
- Stitching now fires for any barge-cancelled send within 20s,
regardless of partial assistant output.
- The workspace-drawer dark rectangle was our own HUX chrome resolving
undefined theme tokens (--bg-primary) to a flat box; bootstrap.css
bridges the real theme tokens and drops a compositor-hazard
backdrop-filter.
- Conversation mode: full-viewport hands-free overlay with an energy-
driven orb (mic RMS in, TTS activity out), state caption, live
transcript/reply captions, mute and exit controls, focus trap,
Escape, reduced-motion support. Voice lane 253 tests green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
2026-08-24 11:21:35 -03:00
|
|
|
/* Idle listening: slow breathing. */
|
|
|
|
|
.voice-conversation[data-voice-state="listening"] .voice-conversation-orb-halo {
|
|
|
|
|
animation: voice-conversation-breathe 4.4s ease-in-out infinite;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Transcribing/thinking: the ring sweeps. */
|
|
|
|
|
.voice-conversation[data-voice-state="transcribing"] .voice-conversation-orb-ring,
|
|
|
|
|
.voice-conversation[data-voice-state="thinking"] .voice-conversation-orb-ring {
|
|
|
|
|
border-top-color: rgba(var(--voice-accent), 0.95);
|
|
|
|
|
border-right-color: rgba(var(--voice-accent-secondary), 0.4);
|
|
|
|
|
animation: voice-conversation-sweep 2.6s linear infinite;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Hermes speaking: the halo waves outward while playback is live. */
|
|
|
|
|
.voice-conversation.is-playing .voice-conversation-orb-halo {
|
|
|
|
|
animation: voice-conversation-wave 1.9s ease-out infinite;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation.is-playing .voice-conversation-orb-core {
|
|
|
|
|
animation: voice-conversation-swell 1.9s ease-in-out infinite;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-state {
|
|
|
|
|
min-height: 20px;
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
font-weight: 650;
|
|
|
|
|
letter-spacing: 0.02em;
|
|
|
|
|
text-align: center;
|
|
|
|
|
color: rgba(238, 246, 255, 0.9);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-captions {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
width: min(100%, 40rem);
|
2026-08-24 13:58:35 -03:00
|
|
|
min-height: 0;
|
hermes(voice): fix first-word clipping, always-stitch, panel glow, conversation mode
Round 2 from live testing:
- Clipping root cause: the endpointer committed on any 1.1s pause, so a
thinking pause after a sentence opener sent one word; a speculative
Whisper pass over that fragment then stalled the real commit ~3.5s on
the Jetson. Young utterances now hold a 1.8s endpoint until 1.2s of
speech accrues, speculative decode waits for 700ms of speech, resume
is unconditional after any 650ms gap (server-frozen snapshots can
never reach commit), and the noise floor is capped so playback echo
cannot deafen onset. Deterministic capture-continuity probe added.
- Stitching now fires for any barge-cancelled send within 20s,
regardless of partial assistant output.
- The workspace-drawer dark rectangle was our own HUX chrome resolving
undefined theme tokens (--bg-primary) to a flat box; bootstrap.css
bridges the real theme tokens and drops a compositor-hazard
backdrop-filter.
- Conversation mode: full-viewport hands-free overlay with an energy-
driven orb (mic RMS in, TTS activity out), state caption, live
transcript/reply captions, mute and exit controls, focus trap,
Escape, reduced-motion support. Voice lane 253 tests green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
2026-08-24 11:21:35 -03:00
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-24 13:58:35 -03:00
|
|
|
/* Each caption is its own bounded scroll region: the user transcript and the
|
|
|
|
|
reply overflow independently, scroll by touch inside the overlay without
|
|
|
|
|
moving the page behind (overscroll-behavior: contain), stay keyboard
|
|
|
|
|
scrollable via tabindex, and fade at the top edge so clipped history reads
|
|
|
|
|
as scrollable. Auto-follow of the streaming tail lives in atlas-voice.js
|
|
|
|
|
(data-follow, per region). */
|
|
|
|
|
.voice-conversation-caption-user,
|
|
|
|
|
.voice-conversation-caption-assistant {
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
touch-action: pan-y;
|
|
|
|
|
-webkit-overflow-scrolling: touch;
|
|
|
|
|
overscroll-behavior: contain;
|
|
|
|
|
scrollbar-width: thin;
|
|
|
|
|
-webkit-mask-image: linear-gradient(180deg, transparent 0, #000 18px, #000 100%);
|
|
|
|
|
mask-image: linear-gradient(180deg, transparent 0, #000 18px, #000 100%);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-caption-user:focus-visible,
|
|
|
|
|
.voice-conversation-caption-assistant:focus-visible {
|
|
|
|
|
outline: 2px solid rgb(var(--voice-accent));
|
|
|
|
|
outline-offset: 2px;
|
|
|
|
|
}
|
|
|
|
|
|
hermes(voice): fix first-word clipping, always-stitch, panel glow, conversation mode
Round 2 from live testing:
- Clipping root cause: the endpointer committed on any 1.1s pause, so a
thinking pause after a sentence opener sent one word; a speculative
Whisper pass over that fragment then stalled the real commit ~3.5s on
the Jetson. Young utterances now hold a 1.8s endpoint until 1.2s of
speech accrues, speculative decode waits for 700ms of speech, resume
is unconditional after any 650ms gap (server-frozen snapshots can
never reach commit), and the noise floor is capped so playback echo
cannot deafen onset. Deterministic capture-continuity probe added.
- Stitching now fires for any barge-cancelled send within 20s,
regardless of partial assistant output.
- The workspace-drawer dark rectangle was our own HUX chrome resolving
undefined theme tokens (--bg-primary) to a flat box; bootstrap.css
bridges the real theme tokens and drops a compositor-hazard
backdrop-filter.
- Conversation mode: full-viewport hands-free overlay with an energy-
driven orb (mic RMS in, TTS activity out), state caption, live
transcript/reply captions, mute and exit controls, focus trap,
Escape, reduced-motion support. Voice lane 253 tests green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
2026-08-24 11:21:35 -03:00
|
|
|
.voice-conversation-caption-user {
|
|
|
|
|
margin: 0;
|
2026-08-24 13:58:35 -03:00
|
|
|
max-height: 22vh;
|
hermes(voice): fix first-word clipping, always-stitch, panel glow, conversation mode
Round 2 from live testing:
- Clipping root cause: the endpointer committed on any 1.1s pause, so a
thinking pause after a sentence opener sent one word; a speculative
Whisper pass over that fragment then stalled the real commit ~3.5s on
the Jetson. Young utterances now hold a 1.8s endpoint until 1.2s of
speech accrues, speculative decode waits for 700ms of speech, resume
is unconditional after any 650ms gap (server-frozen snapshots can
never reach commit), and the noise floor is capped so playback echo
cannot deafen onset. Deterministic capture-continuity probe added.
- Stitching now fires for any barge-cancelled send within 20s,
regardless of partial assistant output.
- The workspace-drawer dark rectangle was our own HUX chrome resolving
undefined theme tokens (--bg-primary) to a flat box; bootstrap.css
bridges the real theme tokens and drops a compositor-hazard
backdrop-filter.
- Conversation mode: full-viewport hands-free overlay with an energy-
driven orb (mic RMS in, TTS activity out), state caption, live
transcript/reply captions, mute and exit controls, focus trap,
Escape, reduced-motion support. Voice lane 253 tests green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
2026-08-24 11:21:35 -03:00
|
|
|
font-size: 14px;
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
color: rgba(178, 196, 214, 0.88);
|
|
|
|
|
font-style: italic;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-caption-assistant {
|
|
|
|
|
margin: 0;
|
2026-08-24 13:58:35 -03:00
|
|
|
max-height: 38vh;
|
hermes(voice): fix first-word clipping, always-stitch, panel glow, conversation mode
Round 2 from live testing:
- Clipping root cause: the endpointer committed on any 1.1s pause, so a
thinking pause after a sentence opener sent one word; a speculative
Whisper pass over that fragment then stalled the real commit ~3.5s on
the Jetson. Young utterances now hold a 1.8s endpoint until 1.2s of
speech accrues, speculative decode waits for 700ms of speech, resume
is unconditional after any 650ms gap (server-frozen snapshots can
never reach commit), and the noise floor is capped so playback echo
cannot deafen onset. Deterministic capture-continuity probe added.
- Stitching now fires for any barge-cancelled send within 20s,
regardless of partial assistant output.
- The workspace-drawer dark rectangle was our own HUX chrome resolving
undefined theme tokens (--bg-primary) to a flat box; bootstrap.css
bridges the real theme tokens and drops a compositor-hazard
backdrop-filter.
- Conversation mode: full-viewport hands-free overlay with an energy-
driven orb (mic RMS in, TTS activity out), state caption, live
transcript/reply captions, mute and exit controls, focus trap,
Escape, reduced-motion support. Voice lane 253 tests green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
2026-08-24 11:21:35 -03:00
|
|
|
font-size: 16px;
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
color: rgba(240, 247, 255, 0.95);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-controls {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 14px;
|
|
|
|
|
margin-top: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-controls button {
|
|
|
|
|
font: inherit;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: rgba(233, 243, 252, 0.92);
|
|
|
|
|
background: rgba(255, 255, 255, 0.07);
|
|
|
|
|
border: 1px solid rgba(var(--voice-accent), 0.36);
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
padding: 10px 18px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-controls button:hover {
|
|
|
|
|
background: rgba(255, 255, 255, 0.12);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-controls button:focus-visible {
|
|
|
|
|
outline: 2px solid rgb(var(--voice-accent));
|
|
|
|
|
outline-offset: 2px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation.is-muted .voice-conversation-mute {
|
|
|
|
|
color: #ffd9c7;
|
|
|
|
|
border-color: rgba(235, 135, 88, 0.6);
|
|
|
|
|
background: rgba(235, 135, 88, 0.14);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-24 15:46:09 -03:00
|
|
|
/* FIX 3: unobtrusive language chooser in the top-right corner. A small frosted
|
|
|
|
|
globe that opens a compact Auto + supported-language menu; selecting forces
|
|
|
|
|
both the reply voice and the STT hint for the session. */
|
|
|
|
|
.voice-conversation-lang {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: calc(16px + env(safe-area-inset-top, 0px));
|
|
|
|
|
right: calc(16px + env(safe-area-inset-right, 0px));
|
|
|
|
|
z-index: 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-lang-btn {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
width: 40px;
|
|
|
|
|
height: 40px;
|
|
|
|
|
padding: 0;
|
|
|
|
|
color: rgba(226, 238, 250, 0.72);
|
|
|
|
|
background: rgba(255, 255, 255, 0.06);
|
|
|
|
|
border: 1px solid rgba(var(--voice-accent), 0.28);
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: color 160ms ease, background 160ms ease, border-color 160ms ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-lang-btn:hover {
|
|
|
|
|
color: rgba(240, 248, 255, 0.95);
|
|
|
|
|
background: rgba(255, 255, 255, 0.12);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-lang-btn:focus-visible {
|
|
|
|
|
outline: 2px solid rgb(var(--voice-accent));
|
|
|
|
|
outline-offset: 2px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Forced (non-Auto) language: a small accent dot marks the active override. */
|
|
|
|
|
.voice-conversation-lang-btn.is-forced {
|
|
|
|
|
color: rgb(var(--voice-accent));
|
|
|
|
|
border-color: rgba(var(--voice-accent), 0.7);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-lang-btn.is-forced::after {
|
|
|
|
|
content: "";
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 2px;
|
|
|
|
|
right: 2px;
|
|
|
|
|
width: 8px;
|
|
|
|
|
height: 8px;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
background: rgb(var(--voice-accent));
|
|
|
|
|
box-shadow: 0 0 6px rgba(var(--voice-accent), 0.8);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-lang-menu {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 46px;
|
|
|
|
|
right: 0;
|
|
|
|
|
min-width: 148px;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 2px;
|
|
|
|
|
padding: 6px;
|
|
|
|
|
background: rgba(16, 22, 36, 0.96);
|
|
|
|
|
border: 1px solid rgba(var(--voice-accent), 0.28);
|
|
|
|
|
border-radius: 14px;
|
|
|
|
|
box-shadow: 0 18px 44px rgba(0, 0, 0, 0.5);
|
|
|
|
|
backdrop-filter: blur(8px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-lang-menu[hidden] {
|
|
|
|
|
display: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-lang-item {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
font: inherit;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: 550;
|
|
|
|
|
text-align: left;
|
|
|
|
|
color: rgba(231, 241, 251, 0.9);
|
|
|
|
|
background: transparent;
|
|
|
|
|
border: 0;
|
|
|
|
|
border-radius: 9px;
|
|
|
|
|
padding: 9px 12px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-lang-item:hover {
|
|
|
|
|
background: rgba(255, 255, 255, 0.08);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-lang-item:focus-visible {
|
|
|
|
|
outline: 2px solid rgb(var(--voice-accent));
|
|
|
|
|
outline-offset: -2px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* The selected option carries a trailing check. */
|
|
|
|
|
.voice-conversation-lang-item[aria-checked="true"] {
|
|
|
|
|
color: #fff;
|
|
|
|
|
background: rgba(var(--voice-accent), 0.16);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-lang-item[aria-checked="true"]::after {
|
|
|
|
|
content: "✓";
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
color: rgb(var(--voice-accent));
|
|
|
|
|
}
|
|
|
|
|
|
hermes(voice): interim-ack tail, natural fillers, unified audio + output picker
Three conversation-mode fixes in one pass:
- Interim-acknowledgement truncation: when an interim ack folds into the
hidden worklog segment mid-speech, the retained unspoken tail is now
flushed and spoken in full before the Thinking transition, and a
distinct follow-up message is chunked from its own start and queued
after the interim drains (no more 'stops after the first clause, rest
resurfaces with the next message').
- Natural thinking fillers: brief per-language interjections (Umm/Hmm/
One sec; Mmm/A ver; Хм/Секунду) on genuine >1.9s thinking gaps only,
non-repeating, answer-preempting, mute-aware.
- One unified audio sink for every spoken output (reply, cues, fillers,
WAV fallback) - fixes cues playing the loudspeaker while the reply
used a different output - plus a tidy corner output-device selector
(enumerateDevices + setSinkId, feature-detected, session-only) styled
like the language selector. Also realigns two STT-server decode-param
assertions to the dict form from the STT tuning commit. 284 voice tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
2026-08-24 17:32:13 -03:00
|
|
|
/* Output-device selector: a twin of the language chooser, seated just left of
|
|
|
|
|
it in the same corner. Feature-detected and hidden entirely when the browser
|
|
|
|
|
cannot enumerate/route outputs, so it never appears as a dead control. */
|
|
|
|
|
.voice-conversation-out {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: calc(16px + env(safe-area-inset-top, 0px));
|
|
|
|
|
right: calc(64px + env(safe-area-inset-right, 0px));
|
|
|
|
|
z-index: 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-out-btn {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
width: 40px;
|
|
|
|
|
height: 40px;
|
|
|
|
|
padding: 0;
|
|
|
|
|
color: rgba(226, 238, 250, 0.72);
|
|
|
|
|
background: rgba(255, 255, 255, 0.06);
|
|
|
|
|
border: 1px solid rgba(var(--voice-accent), 0.28);
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: color 160ms ease, background 160ms ease, border-color 160ms ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-out-btn:hover {
|
|
|
|
|
color: rgba(240, 248, 255, 0.95);
|
|
|
|
|
background: rgba(255, 255, 255, 0.12);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-out-btn:focus-visible {
|
|
|
|
|
outline: 2px solid rgb(var(--voice-accent));
|
|
|
|
|
outline-offset: 2px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* A non-default output device is marked with the same accent dot. */
|
|
|
|
|
.voice-conversation-out-btn.is-forced {
|
|
|
|
|
color: rgb(var(--voice-accent));
|
|
|
|
|
border-color: rgba(var(--voice-accent), 0.7);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-out-btn.is-forced::after {
|
|
|
|
|
content: "";
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 2px;
|
|
|
|
|
right: 2px;
|
|
|
|
|
width: 8px;
|
|
|
|
|
height: 8px;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
background: rgb(var(--voice-accent));
|
|
|
|
|
box-shadow: 0 0 6px rgba(var(--voice-accent), 0.8);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-out-menu {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 46px;
|
|
|
|
|
right: 0;
|
|
|
|
|
min-width: 180px;
|
|
|
|
|
max-width: 260px;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 2px;
|
|
|
|
|
padding: 6px;
|
|
|
|
|
background: rgba(16, 22, 36, 0.96);
|
|
|
|
|
border: 1px solid rgba(var(--voice-accent), 0.28);
|
|
|
|
|
border-radius: 14px;
|
|
|
|
|
box-shadow: 0 18px 44px rgba(0, 0, 0, 0.5);
|
|
|
|
|
backdrop-filter: blur(8px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-out-menu[hidden] {
|
|
|
|
|
display: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-out-item {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
font: inherit;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: 550;
|
|
|
|
|
text-align: left;
|
|
|
|
|
color: rgba(231, 241, 251, 0.9);
|
|
|
|
|
background: transparent;
|
|
|
|
|
border: 0;
|
|
|
|
|
border-radius: 9px;
|
|
|
|
|
padding: 9px 12px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-out-item:hover {
|
|
|
|
|
background: rgba(255, 255, 255, 0.08);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-out-item:focus-visible {
|
|
|
|
|
outline: 2px solid rgb(var(--voice-accent));
|
|
|
|
|
outline-offset: -2px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-out-item[aria-checked="true"] {
|
|
|
|
|
color: #fff;
|
|
|
|
|
background: rgba(var(--voice-accent), 0.16);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-out-item[aria-checked="true"]::after {
|
|
|
|
|
content: "✓";
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
color: rgb(var(--voice-accent));
|
|
|
|
|
}
|
|
|
|
|
|
hermes(voice): fix first-word clipping, always-stitch, panel glow, conversation mode
Round 2 from live testing:
- Clipping root cause: the endpointer committed on any 1.1s pause, so a
thinking pause after a sentence opener sent one word; a speculative
Whisper pass over that fragment then stalled the real commit ~3.5s on
the Jetson. Young utterances now hold a 1.8s endpoint until 1.2s of
speech accrues, speculative decode waits for 700ms of speech, resume
is unconditional after any 650ms gap (server-frozen snapshots can
never reach commit), and the noise floor is capped so playback echo
cannot deafen onset. Deterministic capture-continuity probe added.
- Stitching now fires for any barge-cancelled send within 20s,
regardless of partial assistant output.
- The workspace-drawer dark rectangle was our own HUX chrome resolving
undefined theme tokens (--bg-primary) to a flat box; bootstrap.css
bridges the real theme tokens and drops a compositor-hazard
backdrop-filter.
- Conversation mode: full-viewport hands-free overlay with an energy-
driven orb (mic RMS in, TTS activity out), state caption, live
transcript/reply captions, mute and exit controls, focus trap,
Escape, reduced-motion support. Voice lane 253 tests green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
2026-08-24 11:21:35 -03:00
|
|
|
.voice-conversation.is-muted .voice-conversation-orb-halo,
|
|
|
|
|
.voice-conversation.is-muted .voice-conversation-orb-core {
|
|
|
|
|
animation: none;
|
|
|
|
|
transform: none;
|
|
|
|
|
filter: blur(10px) saturate(0.4);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation.is-muted .voice-conversation-orb-core {
|
|
|
|
|
filter: saturate(0.35) brightness(0.8);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-24 15:46:09 -03:00
|
|
|
/* 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. */
|
|
|
|
|
.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: ""; }
|
|
|
|
|
}
|
|
|
|
|
|
hermes(voice): fix first-word clipping, always-stitch, panel glow, conversation mode
Round 2 from live testing:
- Clipping root cause: the endpointer committed on any 1.1s pause, so a
thinking pause after a sentence opener sent one word; a speculative
Whisper pass over that fragment then stalled the real commit ~3.5s on
the Jetson. Young utterances now hold a 1.8s endpoint until 1.2s of
speech accrues, speculative decode waits for 700ms of speech, resume
is unconditional after any 650ms gap (server-frozen snapshots can
never reach commit), and the noise floor is capped so playback echo
cannot deafen onset. Deterministic capture-continuity probe added.
- Stitching now fires for any barge-cancelled send within 20s,
regardless of partial assistant output.
- The workspace-drawer dark rectangle was our own HUX chrome resolving
undefined theme tokens (--bg-primary) to a flat box; bootstrap.css
bridges the real theme tokens and drops a compositor-hazard
backdrop-filter.
- Conversation mode: full-viewport hands-free overlay with an energy-
driven orb (mic RMS in, TTS activity out), state caption, live
transcript/reply captions, mute and exit controls, focus trap,
Escape, reduced-motion support. Voice lane 253 tests green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
2026-08-24 11:21:35 -03:00
|
|
|
@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))); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes voice-conversation-sweep {
|
|
|
|
|
0% { transform: rotate(0deg); }
|
|
|
|
|
100% { transform: rotate(360deg); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes voice-conversation-wave {
|
|
|
|
|
0% { opacity: 0.8; transform: scale(0.96); }
|
|
|
|
|
55% { opacity: 0.5; transform: scale(1.12); }
|
|
|
|
|
100% { opacity: 0.75; transform: scale(0.98); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes voice-conversation-swell {
|
|
|
|
|
0%, 100% { transform: scale(0.99); }
|
|
|
|
|
50% { transform: scale(1.05); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 640px) {
|
|
|
|
|
.voice-conversation { gap: 18px; }
|
2026-08-24 13:58:35 -03:00
|
|
|
.voice-conversation-caption-user { max-height: 24vh; }
|
|
|
|
|
.voice-conversation-caption-assistant { max-height: 34vh; }
|
hermes(voice): fix first-word clipping, always-stitch, panel glow, conversation mode
Round 2 from live testing:
- Clipping root cause: the endpointer committed on any 1.1s pause, so a
thinking pause after a sentence opener sent one word; a speculative
Whisper pass over that fragment then stalled the real commit ~3.5s on
the Jetson. Young utterances now hold a 1.8s endpoint until 1.2s of
speech accrues, speculative decode waits for 700ms of speech, resume
is unconditional after any 650ms gap (server-frozen snapshots can
never reach commit), and the noise floor is capped so playback echo
cannot deafen onset. Deterministic capture-continuity probe added.
- Stitching now fires for any barge-cancelled send within 20s,
regardless of partial assistant output.
- The workspace-drawer dark rectangle was our own HUX chrome resolving
undefined theme tokens (--bg-primary) to a flat box; bootstrap.css
bridges the real theme tokens and drops a compositor-hazard
backdrop-filter.
- Conversation mode: full-viewport hands-free overlay with an energy-
driven orb (mic RMS in, TTS activity out), state caption, live
transcript/reply captions, mute and exit controls, focus trap,
Escape, reduced-motion support. Voice lane 253 tests green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
2026-08-24 11:21:35 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Reduced motion: static orb states — the accent color and the state caption
|
|
|
|
|
still communicate every phase without animation or level-driven motion. */
|
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
|
|
|
.voice-conversation *,
|
|
|
|
|
.voice-conversation *::before,
|
|
|
|
|
.voice-conversation *::after {
|
|
|
|
|
animation: none !important;
|
|
|
|
|
transition: none !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.voice-conversation-orb-halo,
|
|
|
|
|
.voice-conversation-orb-core {
|
|
|
|
|
transform: none !important;
|
|
|
|
|
}
|
|
|
|
|
}
|