npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

neutral-karaoke

v0.0.1

Published

Word-by-word text reveal component synced with TTS events

Readme

neutral-karaoke

Word-by-word text reveal component that syncs with TTS (Text-to-Speech) events. Creates a "karaoke" effect where words are revealed as they are spoken.

Installation

npm install neutral-karaoke neutral-agent
# or
pnpm add neutral-karaoke neutral-agent

Peer Dependencies

This package requires:

  • react >= 18
  • react-dom >= 18
  • neutral-agent (provides TTS state management via useTtsStore)

CSS Requirements

The component uses these Tailwind CSS classes that must be available in your project:

| Class | Purpose | | -------------------------- | ---------------------------- | | text-muted-foreground/60 | Unrevealed (dimmed) word styling | | text-foreground | Revealed word styling | | transition-colors | Color transition animation | | duration-75 | Transition timing (75ms) |

If you're not using Tailwind, you'll need to provide equivalent CSS for [data-reveal-word] elements.

Custom CSS Example (non-Tailwind)

[data-reveal-word] {
  transition: color 75ms ease;
}

[data-reveal-word].text-muted-foreground\/60 {
  color: rgba(113, 113, 122, 0.6); /* Dimmed color */
}

[data-reveal-word].text-foreground {
  color: rgb(250, 250, 250); /* Revealed color */
}

Usage

Basic Usage with RevealText Component

import { RevealText, useWordReveal } from "neutral-karaoke";

function MyMessage({ text, messageId }) {
  const { revealedCount, isComplete } = useWordReveal(text, {
    enabled: true,
    revealMessageId: messageId,
    useExternalCount: true, // Use TTS events for reveal timing
  });

  return (
    <RevealText revealedCount={revealedCount} enabled={!isComplete}>
      <YourMarkdownRenderer>{text}</YourMarkdownRenderer>
    </RevealText>
  );
}

API Reference

useWordReveal(text, options)

Hook that manages word reveal state based on TTS events.

Parameters:

  • text: string - The text to reveal
  • options.enabled?: boolean - Enable/disable reveal effect (default: true)
  • options.revealMessageId?: string - Message ID for this reveal session
  • options.useExternalCount?: boolean - Use TTS word count for timing (default: false)

Returns:

{
  revealedCount: number;     // Number of words revealed
  isComplete: boolean;       // Whether all words are revealed
  getRevealedText: () => string;  // Get text up to revealed count
  stopReveal: () => { text: string; rawPosition: number };
  needsTruncation: () => boolean;
  isTruncationNeeded: boolean;
}

<RevealText>

Component that wraps content and applies reveal styling to words.

Props:

| Prop | Type | Default | Description | | -------------- | ----------- | ------- | ----------------------------------------- | | children | ReactNode | - | Content to reveal (typically markdown) | | revealedCount| number | - | Number of words to show as revealed | | enabled | boolean | true | Enable the reveal effect | | className | string | - | Additional CSS classes |

Types

import type {
  RevealState,
  RevealTextProps,
  WordRevealOptions,
  WordRevealState,
} from "neutral-karaoke";

WordRevealOptions

type WordRevealOptions = {
  enabled?: boolean;
  revealMessageId?: string;
  useExternalCount?: boolean;
};

WordRevealState

type WordRevealState = {
  revealedCount: number;
  isComplete: boolean;
  getRevealedText: () => string;
  stopReveal: () => { text: string; rawPosition: number };
  needsTruncation: () => boolean;
  isTruncationNeeded: boolean;
};

RevealState

type RevealState = {
  messageId: string;
  isStreaming: boolean;
  isRevealComplete: boolean;
  getRevealedText: () => string;
  stopReveal: () => { text: string; rawPosition: number };
  needsTruncation: () => boolean;
};

Integration with neutral-agent

This package is designed to work with neutral-agent's TTS system. The useTtsStore from that package provides:

  • spokenWordCount - Number of words spoken by TTS
  • messageId - Current message being spoken

When useExternalCount: true, the reveal timing is driven by TTS events, creating synchronized karaoke-style text reveal.

Example: Chat Message with Voice

import { useWordReveal, RevealText, type RevealState } from "neutral-karaoke";

function ChatMessage({ message, isVoiceActive, onRevealStateChange }) {
  const {
    revealedCount,
    isComplete,
    getRevealedText,
    stopReveal,
    needsTruncation,
  } = useWordReveal(message.text, {
    enabled: message.role === "assistant",
    revealMessageId: message.id,
    useExternalCount: isVoiceActive,
  });

  // Report reveal state to parent for interruption handling
  useEffect(() => {
    onRevealStateChange?.({
      messageId: message.id,
      isStreaming: false,
      isRevealComplete: isComplete,
      getRevealedText,
      stopReveal,
      needsTruncation,
    });
  }, [isComplete]);

  return (
    <RevealText revealedCount={revealedCount} enabled={!isComplete}>
      <Markdown>{message.text}</Markdown>
    </RevealText>
  );
}

How It Works

  1. DOM Manipulation: RevealText uses a MutationObserver to detect new text content and wraps each word in a <span> with a data-word-index attribute.

  2. CSS Transitions: Words are styled with text-muted-foreground/60 (dimmed) or text-foreground (revealed) based on their index relative to revealedCount.

  3. TTS Sync: useWordReveal subscribes to useTtsStore from neutral-agent to get the current spoken word count.

  4. Markdown Handling: The hook cleans markdown syntax (code blocks, list markers, etc.) when calculating word positions for accurate truncation.

  5. Interruption Support: stopReveal() freezes the reveal count and returns the truncated text with its position in the raw (unprocessed) text.

License

ISC