npm package: @kids-games/core
Description: Shared framework for Kids Corner games — voice personas, audio playback, celebrations, UI components, and design tokens
Latest version: 1.0.0

# @kids-games/core

Shared framework for [Kids Corner](https://kids.mattatencio.com) educational games (ages 2-6). Provides voice personas with pre-generated TTS audio, sound effects, celebration animations, reusable React components, and design tokens.

## Installation

```bash
npm install @kids-games/core
```

Peer dependencies (install if not already present):

```bash
npm install react@^19 react-dom@^19
```

## Modules

The package is split into five independently importable modules:

| Module | Import | Description |
|--------|--------|-------------|
| **Voice** | `@kids-games/core/voice` | Persona system, audio playback, React provider & hooks |
| **SFX** | `@kids-games/core/sfx` | Synthesized sound effects via Web Audio API |
| **Celebrations** | `@kids-games/core/celebrations` | Confetti particle system with physics |
| **Components** | `@kids-games/core/components` | Ready-to-use React UI components |
| **Tokens** | `@kids-games/core/tokens` | Design tokens (colors, spacing, touch targets) |

---

## Voice

Five kid-friendly personas, each mapped to an OpenAI TTS voice with a Web Speech API fallback:

| Persona | Voice | Character |
|---------|-------|-----------|
| Sunny | shimmer | Warm, encouraging guide |
| Bloop | fable | Silly, playful buddy |
| Maple | nova | Calm, nurturing mentor |
| Fizz | echo | Energetic, excited hype |
| Pip | alloy | Gentle, curious explorer |

### Provider setup

Wrap your app (or game layout) with `KidsAudioProvider`:

```tsx
import { KidsAudioProvider } from "@kids-games/core/voice";

export default function Layout({ children }) {
  return (
    <KidsAudioProvider storagePrefix="color-mixer" audioBasePath="/audio">
      {children}
    </KidsAudioProvider>
  );
}
```

### Hooks

```tsx
import { useVoice, usePersona, useSoundEffects, useCelebrate } from "@kids-games/core/voice";

function GameScreen() {
  const { playPhrase, muted, toggleMute } = useVoice();
  const persona = usePersona("sunny");
  const { playCorrect, playIncorrect } = useSoundEffects();
  const celebrate = useCelebrate("sunny");

  const handleAnswer = (correct: boolean) => {
    if (correct) {
      playCorrect();
      celebrate("big");
      playPhrase("well-done", "sunny");
    } else {
      playIncorrect();
      playPhrase("try-again", "sunny");
    }
  };

  return <button onClick={() => handleAnswer(true)}>Answer</button>;
}
```

### Persona data

```tsx
import { PERSONAS, getPersona, getRandomPhrase } from "@kids-games/core/voice";

const sunny = getPersona("sunny");
// { name: "Sunny", openaiVoice: "shimmer", ... }

const phrase = getRandomPhrase("sunny", "celebration");
// Random celebration phrase from Sunny's pool
```

---

## SFX

Synthesized sound effects using the Web Audio API. No audio files needed.

```tsx
import { playTap, playPop, playCorrect, playIncorrect, playWhoosh, playCelebrationChime } from "@kids-games/core/sfx";

// All functions take an AudioContext
const ctx = new AudioContext();
playTap(ctx);
playPop(ctx);
playCorrect(ctx);
playIncorrect(ctx);
playWhoosh(ctx);
playCelebrationChime(ctx);
```

When using with the voice module, get the context from the provider:

```tsx
const { getContext } = useVoice();
playTap(getContext());
```

---

## Celebrations

Confetti particle system with three intensity levels.

```tsx
import { createConfettiBurst, tickConfetti } from "@kids-games/core/celebrations";
import { CELEBRATION_CONFIGS } from "@kids-games/core/celebrations";
import type { CelebrationLevel } from "@kids-games/core/components";

// Create particles
const config = CELEBRATION_CONFIGS["big"]; // small | medium | big
let particles = createConfettiBurst(config.particleCount);

// Animation loop
function animate() {
  particles = tickConfetti(particles);
  // render particles...
  if (particles.length > 0) requestAnimationFrame(animate);
}
animate();
```

Or use the ready-made component (see Components below).

---

## Components

### CelebrationOverlay

Full-screen confetti overlay with auto-dismiss.

```tsx
import { CelebrationOverlay } from "@kids-games/core/components";

<CelebrationOverlay
  level="big"        // "small" | "medium" | "big" | null
  text="Great job!"  // optional overlay text
  onComplete={() => setLevel(null)}
/>
```

### MuteButton

Floating mute/unmute toggle. Requires `KidsAudioProvider` as an ancestor.

```tsx
import { MuteButton } from "@kids-games/core/components";

<MuteButton size={48} className="custom-class" />
```

### PersonaPicker

Tappable persona cards with localStorage persistence.

```tsx
import { PersonaPicker } from "@kids-games/core/components";

<PersonaPicker
  availablePersonas={["sunny", "bloop", "maple"]}
  onSelect={(id) => setActivePersona(id)}
  storageKey="my-game-persona"
/>
```

---

## Tokens

Design tokens optimized for children ages 2-6.

### Colors

```tsx
import { COLORS } from "@kids-games/core/tokens";

COLORS.primary.red      // "#FF6B6B"
COLORS.background.warm  // "#FFF8E7"
COLORS.feedback.success // "#4CAF50"
COLORS.persona.sunny    // "#FFD93D"
```

### Spacing

```tsx
import { SPACING } from "@kids-games/core/tokens";

SPACING.touchTarget.min          // 48 (WCAG minimum)
SPACING.touchTarget.recommended  // 56 (toddler-friendly)
SPACING.touchTarget.large        // 64 (primary actions)
SPACING.padding.md               // 16
SPACING.gap.comfortable          // 16
SPACING.radius.lg                // 16
SPACING.fontSize.lg              // 24
```

---

## Generating Voice Audio

Each game defines its dialogue in a `voice-script.json` file. The generation script converts these to MP3 files using the OpenAI TTS API.

### 1. Create a voice script

```json
{
  "lines": [
    { "id": "welcome", "text": "Welcome to Color Mixer!", "persona": "sunny" },
    { "id": "try-again", "text": "Oops, let's try that again!", "persona": "bloop" },
    { "id": "well-done", "text": "Amazing job, you did it!", "persona": "sunny" }
  ]
}
```

Save this as `voice-script.json` in your game's root directory.

### 2. Generate audio files

```bash
OPENAI_API_KEY=sk-... npx tsx node_modules/@kids-games/core/scripts/generate-audio.ts ./
```

Or from the monorepo root:

```bash
OPENAI_API_KEY=sk-... npx tsx _core/scripts/generate-audio.ts ./my-game/
```

This creates `public/audio/<persona>/<id>.mp3` files. Use `--force` to regenerate existing files.

### 3. Reference in your provider

```tsx
<KidsAudioProvider storagePrefix="my-game" audioBasePath="/audio">
```

The player resolves audio as `{audioBasePath}/{persona}/{phraseId}.mp3`.

---

## TypeScript

All exports are fully typed. Key types:

```tsx
import type {
  PersonaId,       // "sunny" | "bloop" | "maple" | "fizz" | "pip"
  Persona,         // Full persona config object
  CelebrationLevel // "small" | "medium" | "big"
} from "@kids-games/core/voice";
```

---

## License

[MIT](./LICENSE)