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. Claude-Session: https://claude.ai/code/session_012G3dUMVDCDL3PwQE4WTQxL Co-authored-by: Claude <noreply@anthropic.com>
64 lines
2.5 KiB
TypeScript
64 lines
2.5 KiB
TypeScript
import { botColors } from "@rakazo/ui-tokens";
|
|
import { describe, expect, it } from "vitest";
|
|
import { auroraSpectrum, captionWords, continuesCaption, voiceAuroraBlobs } from "./voice-aurora";
|
|
|
|
describe("auroraSpectrum", () => {
|
|
it("leads with the bot's own color and keeps the whole palette", () => {
|
|
const color = botColors[3];
|
|
const spectrum = auroraSpectrum(color.toLowerCase());
|
|
expect(spectrum[0]).toBe(color);
|
|
expect([...spectrum].sort()).toEqual([...botColors].sort());
|
|
});
|
|
|
|
it("puts an unknown color first, ahead of the palette", () => {
|
|
expect(auroraSpectrum("#123456")).toEqual(["#123456", ...botColors]);
|
|
});
|
|
});
|
|
|
|
describe("voiceAuroraBlobs", () => {
|
|
it("centers the bot color and fans out symmetrically", () => {
|
|
const blobs = voiceAuroraBlobs(botColors[0], "speaking", 0);
|
|
expect(blobs.map((blob) => blob.x)).toEqual([50, 32, 68, 14, 86]);
|
|
expect(blobs[0]?.color).toBe(botColors[0]);
|
|
expect(blobs[1]?.rise).toBe(blobs[2]?.rise);
|
|
expect(blobs[0]?.rise).toBeGreaterThan(blobs[3]?.rise ?? 0);
|
|
});
|
|
|
|
it("rises with mic energy while listening", () => {
|
|
const quiet = voiceAuroraBlobs(botColors[0], "listening", 0)[0];
|
|
const loud = voiceAuroraBlobs(botColors[0], "listening", 1)[0];
|
|
expect(loud?.rise).toBeGreaterThan(quiet?.rise ?? 0);
|
|
expect(loud?.opacity).toBeGreaterThan(quiet?.opacity ?? 0);
|
|
});
|
|
|
|
it("ignores energy outside listening and clamps bad input", () => {
|
|
expect(voiceAuroraBlobs(botColors[0], "thinking", 1)).toEqual(
|
|
voiceAuroraBlobs(botColors[0], "thinking", 0),
|
|
);
|
|
expect(voiceAuroraBlobs(botColors[0], "listening", 5)).toEqual(
|
|
voiceAuroraBlobs(botColors[0], "listening", 1),
|
|
);
|
|
expect(voiceAuroraBlobs(botColors[0], "listening", Number.NaN)).toEqual(
|
|
voiceAuroraBlobs(botColors[0], "listening", 0),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("live caption", () => {
|
|
it("splits on any whitespace", () => {
|
|
expect(captionWords(" Set a\ttimer \n")).toEqual(["Set", "a", "timer"]);
|
|
expect(captionWords(" ")).toEqual([]);
|
|
});
|
|
|
|
it("treats appended words and a completing last word as a continuation", () => {
|
|
expect(continuesCaption("Set a", "Set a timer for ten")).toBe(true);
|
|
expect(continuesCaption("remind me to wat", "remind me to water the plants")).toBe(true);
|
|
});
|
|
|
|
it("restarts for a new utterance", () => {
|
|
expect(continuesCaption("", "Hello")).toBe(false);
|
|
expect(continuesCaption("Set a timer", "Set a")).toBe(false);
|
|
expect(continuesCaption("Set a timer", "Get a timer now")).toBe(false);
|
|
});
|
|
});
|