npm package: @supernal/tts-widget
Description: Embeddable TTS widget for blogs and websites - as simple as Google Analytics
Author: Supernal Intelligence
Latest version: 1.0.10

# @supernal/tts-widget

Embeddable Text-to-Speech widget for blogs and websites. Add voice to your content in seconds - just like Google Analytics.

## Features

✅ **Simple as gtag** - Drop in two scripts and you're done
✅ **Zero JavaScript** - Works with plain HTML, no bundlers needed
✅ **Auto-discovery** - Automatically finds and activates TTS elements
✅ **Framework agnostic** - Works with React, Vue, vanilla HTML, etc.
✅ **Smart defaults** - Hardcoded to https://www.tts.supernal.ai, just add your API key

## Quick Start (Recommended)

### The gtag Way - No npm, No bundler

```html
<!-- In your <head> -->
<link rel="stylesheet" href="https://unpkg.com/@supernal/tts-widget@1/dist/widget.css">

<!-- Before </body> -->
<script src="https://unpkg.com/@supernal/tts-widget@1/dist/widget.js"></script>
<script>
  window.SupernalTTS.init({
    apiKey: 'YOUR_API_KEY'
  });
</script>

<!-- Use anywhere on your page -->
<div class="supernal-tts-widget" data-text="This text will have TTS!">
  This text will have TTS!
</div>
```

**That's it!** The widget automatically scans for `.supernal-tts-widget` elements when the page loads.

### Next.js / React

```tsx
import Script from 'next/script';

export default function RootLayout({ children }) {
  return (
    <html>
      <head>
        <link rel="stylesheet" href="https://unpkg.com/@supernal/tts-widget@1/dist/widget.css" />
      </head>
      <body>
        {children}
        <Script src="https://unpkg.com/@supernal/tts-widget@1/dist/widget.js" strategy="afterInteractive" />
        <Script id="tts-init" strategy="afterInteractive">
          {`
            window.SupernalTTS && window.SupernalTTS.init({
              apiKey: '${process.env.NEXT_PUBLIC_TTS_API_KEY}'
            });
          `}
        </Script>
      </body>
    </html>
  );
}
```

Then use in your components:

```tsx
export default function BlogPost({ content }) {
  return (
    <article className="supernal-tts-widget" data-text={content}>
      {content}
    </article>
  );
}
```

## Installation (Optional - for npm workflows)

```bash
npm install @supernal/tts-widget
```

### Option 1: Simple Import (No auto-scan)

```javascript
import { SupernalTTS } from '@supernal/tts-widget';
import '@supernal/tts-widget/widget.css';

SupernalTTS.init({
  apiKey: 'YOUR_API_KEY'
});
```

### Option 2: React Component

```tsx
import { TTSInitializer } from '@supernal/tts-widget/react';
import '@supernal/tts-widget/widget.css';

export default function App() {
  return (
    <>
      <TTSInitializer
        apiKey={process.env.NEXT_PUBLIC_TTS_API_KEY}
        mode="bundled"  // Skip CDN attempt in bundled apps
      />
      <YourContent />
    </>
  );
}
```

## Configuration Options

### Global Init (All defaults built-in)

```javascript
window.SupernalTTS.init({
  // Required (defaults to https://www.tts.supernal.ai if not provided)
  apiKey: 'YOUR_API_KEY',

  // Optional overrides (these all have smart defaults)
  apiUrl: 'https://www.tts.supernal.ai',  // Custom API endpoint
  provider: 'openai',                      // TTS provider
  voice: 'alloy',                          // Default voice
  speed: 1.0,                              // Playback speed (0.25-4.0)
  clientSideSpeed: true,                   // Use browser speed (saves $$, recommended!)
  showBranding: true,                      // Show "Powered by Supernal"
  devMode: false                           // Enable debug logging
});
```

### Per-Widget Options

```html
<!-- Override voice for specific widget -->
<div class="supernal-tts-widget"
     data-text="Professional narration"
     data-voice="nova"
     data-provider="openai">
  Professional narration
</div>

<!-- Advanced controls -->
<div class="supernal-tts-widget"
     data-text="Article content"
     data-voices="alloy,echo,fable"     <!-- Multiple voice options -->
     data-enable-speed="true"            <!-- Show speed slider -->
     data-enable-progress="true">        <!-- Show progress bar -->
  Article content
</div>
```

## Advanced: Programmatic Control

### Manual Widget Addition

```javascript
const tts = window.SupernalTTS.getInstance();

// Add TTS to any element
tts.addWidget(
  document.querySelector('#my-element'),
  'Text to speak',
  { voice: 'alloy', provider: 'openai' }
);
```

### Zero-Config Auto-Init

For truly zero-config, use data attributes:

```html
<script src="https://unpkg.com/@supernal/tts-widget@1/dist/widget.js"
        data-supernal-tts-auto-init='{"apiKey":"YOUR_KEY"}'></script>
```

The widget reads the config and initializes automatically!

## Version Pinning

```html
<!-- Auto-update within v1.x.x (recommended) -->
<script src="https://unpkg.com/@supernal/tts-widget@1/dist/widget.js"></script>

<!-- Lock to specific version -->
<script src="https://unpkg.com/@supernal/tts-widget@1.0.4/dist/widget.js"></script>

<!-- Always latest (not recommended for production) -->
<script src="https://unpkg.com/@supernal/tts-widget@latest/dist/widget.js"></script>
```

### Automatic API Key Detection

The widget automatically detects API keys from common framework environment variables, eliminating boilerplate:

```javascript
// ✅ Minimal configuration - API key auto-detected
SupernalTTS.init({
  apiUrl: window.location.origin
  // provider defaults to 'openai'
  // apiKey auto-detected from NEXT_PUBLIC_TTS_API_KEY (or similar)
});
```

**Supported environment variables (checked in order):**
- `NEXT_PUBLIC_TTS_API_KEY` - Next.js
- `VITE_TTS_API_KEY` - Vite
- `PUBLIC_TTS_API_KEY` - SvelteKit, Astro
- `REACT_APP_TTS_API_KEY` - Create React App

**How it works:**
1. Widget checks for these variables at runtime (bundlers expose them client-side)
2. If found, automatically uses the key for API authentication
3. You can still override by explicitly passing `apiKey` option
4. Only works in browser (server-side rendering won't auto-detect)

**Example - Next.js:**
```bash
# .env.local
NEXT_PUBLIC_TTS_API_KEY=your-api-key-here
```

```javascript
// No need to pass apiKey - auto-detected!
SupernalTTS.init({
  apiUrl: 'https://tts.supernal.ai'
});
```

### Client-Side Speed Adjustment

By default (`clientSideSpeed: true`), the widget uses the browser's native `preservesPitch` feature with `playbackRate` to adjust speed instantly without regenerating audio. This:

- ✅ **Saves significant costs** - no need to generate audio at multiple speeds
- ✅ **Instant speed changes** - no loading/regeneration delay
- ✅ **Maintains pitch quality** - uses browser's time-stretching algorithm
- ✅ Works with already-cached audio

Set `clientSideSpeed: false` if you need server-side speed generation with provider-specific pitch correction (more expensive but may have slightly different quality characteristics for extreme speed changes).

## Widget Data Attributes

```html
<!-- Custom voice per widget -->
<div class="tts-widget" 
     data-text="Professional voice example" 
     data-voice="coral" 
     data-provider="openai">
  Professional voice example
</div>

<!-- Custom speed -->
<div class="tts-widget" 
     data-text="Fast speech" 
     data-speed="1.5">
  Fast speech
</div>
```

## Available Voices

### OpenAI
- `alloy`, `echo`, `fable`, `onyx`, `nova`, `shimmer`, `coral`, `sage`, `verse`

### Mock (Testing)
- `mock-voice-1`, `mock-voice-2`, `mock-voice-3`

## When to Use What?

| Scenario | Recommended Approach | Why |
|----------|---------------------|-----|
| **New projects** | `import { WidgetLoader } from '.../ loader'` | Auto-updates, has fallback |
| **Existing code** | `import { SupernalTTS } from '...'` | No changes needed |
| **Corporate firewall** | `import { SupernalTTS } from '...'` | Works offline/air-gapped |
| **Static HTML blog** | Direct CDN `@1` | Simple, no build step |
| **Need predictability** | `import { SupernalTTS } from '...'` | Manual upgrades only |
| **CSP restrictions** | `import { SupernalTTS } from '...'` | No external scripts |

## Migration from v1.2.x

See [MIGRATION.md](MIGRATION.md) for detailed upgrade guide.

**TL;DR:** No breaking changes. Existing code works as-is. New smart loader is opt-in.

```javascript
// Old (v1.2.x - still works exactly the same)
import { SupernalTTS } from '@supernal/tts-widget';
SupernalTTS.init({ ... });

// New (v1.3.0 - opt-in for auto-updates)
import { WidgetLoader } from '@supernal/tts-widget/loader';
const widget = await WidgetLoader.load();
widget.init({ ... });
```

**No action required for existing code!** The default export is unchanged.

## Documentation

Full documentation: [https://tts.supernal.ai](https://tts.supernal.ai)

---

## Development

This is the **SOURCE OF TRUTH** for the Supernal TTS widget. All changes should be made here.

### Directory Structure

```
packages/@supernal/tts-widget/
├── src/
│   ├── widget.ts    # TypeScript source (EDIT THIS)
│   └── widget.css   # Styles
├── dist/            # Built output (DO NOT EDIT)
│   ├── widget.js    # Bundled JavaScript
│   ├── widget.css   # Copied styles
│   └── widget.d.ts  # TypeScript declarations
├── package.json
└── tsconfig.json
```

### Build Process

1. **Build the widget**:
   ```bash
   npm run build
   ```

   This runs:
   - `tsc` - Compiles TypeScript to JavaScript
   - `esbuild` - Bundles and minifies to a single IIFE file
   - `cp` - Copies CSS to dist

2. **From monorepo root**:
   ```bash
   npm run build:widget
   ```

3. **Docs site** automatically copies widget on build/start:
   ```bash
   cd docs-site
   npm run copy-widget  # or `npm start` / `npm run build`
   ```

### Features

- **TypeScript**: Proper typing and IDE support
- **Dev Mode Cache Clear**: Red button in dev mode to clear local cache
- **Absolute URLs**: Handles both relative and absolute audio URLs
- **Branding**: Optional Supernal Intelligence badge
- **Responsive**: Mobile-friendly design
- **Dark Mode**: Automatic dark mode support

### Usage in Docs Site

The docs-site imports the widget from this package via:
- **Build script**: `copy-widget` in `docs-site/package.json` copies built files to `static/`
- **Component**: `TTSWidget` in `docs-site/src/components/TTSWidget/` loads from `/js/widget.js`

**DO NOT** edit files in `docs-site/static/js/` or `docs-site/static/css/` directly!

### Making Changes

1. Edit `src/widget.ts` or `src/widget.css`
2. Run `npm run build` (or `npm run dev` for watch mode)
3. Test in docs-site: `cd ../../../docs-site && npm start`
4. The widget will be automatically copied and loaded

### Publishing

When ready to publish to npm:

```bash
npm version patch|minor|major
npm publish
```

## Integration

See the main [README](../../../README.md) and [docs-site](../../../docs-site/) for integration examples.