diff --git a/js/src/features/particles/transcript-overlay.tsx b/js/src/features/particles/transcript-overlay.tsx index bd52f15..a8d928d 100644 --- a/js/src/features/particles/transcript-overlay.tsx +++ b/js/src/features/particles/transcript-overlay.tsx @@ -1,4 +1,4 @@ -import { useMemo } from "react"; +import { useMemo, useRef } from "react"; import type { Transcript } from "@/api/types"; type Sentence = Transcript["paragraphs"][number]["sentences"][number]; @@ -41,15 +41,33 @@ export function TranscriptOverlay({ const activeWord = activeWordIndex !== null ? transcript.words[activeWordIndex] : null; - // Find which chunk contains the active word + // Remember the last spoken word so highlights hold during pauses + const lastSpokenWordRef = useRef(null); + if (activeWord) { + lastSpokenWordRef.current = activeWord; + } + const highlightWord = activeWord ?? lastSpokenWordRef.current; + + const lastChunkRef = useRef(null); + + // Find which chunk contains the active word, holding the last one during pauses const activeChunk = useMemo(() => { - if (!activeWord) return chunks[0] ?? null; - for (const chunk of chunks) { - if (chunk.some((w) => w.start === activeWord.start && w.end === activeWord.end)) { - return chunk; + if (activeWord) { + for (const chunk of chunks) { + if (chunk.some((w) => w.start === activeWord.start && w.end === activeWord.end)) { + lastChunkRef.current = chunk; + return chunk; + } } } - return chunks[0] ?? null; + // No active word (speaker pausing) — hold the last chunk + if (lastChunkRef.current && chunks.some((c) => c === lastChunkRef.current)) { + return lastChunkRef.current; + } + // Sentence changed, last chunk no longer valid — use first chunk + const fallback = chunks[0] ?? null; + lastChunkRef.current = fallback; + return fallback; }, [chunks, activeWord]); if (!activeSentence || !activeChunk || activeChunk.length === 0) return null; @@ -62,7 +80,7 @@ export function TranscriptOverlay({

{activeChunk.map((word, i) => { const isSpoken = - activeWord !== null && word.start <= activeWord.end; + highlightWord !== null && word.start <= highlightWord.end; return (