evOo-local/apps/web/src/components/voice/VoiceLiveText.tsx
Claude 0703d79b93
feat(web): aurora voice stage with live word-by-word caption
Replace the full-bleed cyan/violet/emerald bloom field with one dark glass
panel: the Oo, a live caption whose new words fade in, and a compact control
row (phase label, transcript, interrupt, hang up). An iridescent aurora rises
from the panel's bottom edge, follows mic energy while listening, drifts while
thinking and blooms while speaking. Its spectrum is the shared bot palette
rotated so the active Oo's color sits in the middle, so no new hex values.

Voice entry buttons drop the hardcoded cyan/amber styling for neutral tokens
plus a thin aurora rim when voice is ready. Dead voice CSS is removed and the
voice e2e now captures the stage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012G3dUMVDCDL3PwQE4WTQxL
2026-10-02 18:58:15 +00:00

31 lines
962 B
TypeScript

import { useRef } from "react";
import { captionWords, continuesCaption } from "../../lib/voice-aurora";
/** Live caption whose new words fade in as they are heard or spoken. */
export function VoiceLiveText({ text, reducedMotion }: { text: string; reducedMotion: boolean }) {
const shown = useRef({ text: "", utterance: 0 });
if (shown.current.text !== text) {
shown.current = {
text,
utterance: continuesCaption(shown.current.text, text)
? shown.current.utterance
: shown.current.utterance + 1,
};
}
const { utterance } = shown.current;
return (
<p
dir="auto"
className="text-balance text-center text-ui-title font-medium leading-snug text-foreground"
>
{captionWords(text).map((word, index) => (
<span key={`${utterance}-${index}`} className={reducedMotion ? undefined : "voice-word-in"}>
{index ? " " : ""}
{word}
</span>
))}
</p>
);
}