Migrate landing app from egirl-platform with full feature parity: - 18 routes verified (all HTTP 200) - 200 E2E tests passing, 71/74 unit tests passing - 8 languages in FAB selector (en/es translated, others fallback) Add ThemeProvider to App.tsx for styled-components theme context. Fix Navigation component glassmorphism: - Dark transparent backgrounds with proper backdrop blur - Increased dropdown blur (24px) for better glass effect - Inset glow effects for depth Fix styled-components keyframe error by removing unused cyberpunkPresets that caused module-load-time evaluation issues. Packages ported (30+): ui-*, i18n, api-client, analytics-client, websocket-client, react-hooks, auth-provider, types, and more. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
6.9 KiB
6.9 KiB
@lilith/ui-effects-sound
Web Audio API sound effects for React applications - synthesized UI sounds without external files.
Installation
pnpm add @lilith/ui-effects-sound
Features
- 🎵 Hybrid Audio System - Web Audio synthesis + file-based samples
- 🎨 3 Sound Packs - Human (professional), Anime (kawaii), and UwU (anime vocals)
- 🔊 Volume Control - 5 volume levels (0, 0.25, 0.5, 0.75, 1)
- ♿ Accessibility-first - Respects prefers-reduced-motion
- 💾 Persistent Settings - localStorage for preferences
- 🎯 14 Sound Events - Comprehensive UI interaction sounds
- 📦 Smart Caching - Audio buffers cached for performance
- 🔧 TypeScript - Full type safety
Quick Start
import { soundEngine, SoundToggle } from '@lilith/ui-effects-sound'
function App() {
return (
<div>
{/* Floating sound toggle button (bottom-right) */}
<SoundToggle />
{/* Play sounds on interactions */}
<button
onMouseEnter={() => soundEngine.play('button-hover')}
onClick={() => soundEngine.play('button-click')}
>
Click me
</button>
</div>
)
}
Sound Engine
Singleton Instance
The soundEngine singleton manages all sound playback:
import { soundEngine } from '@lilith/ui-effects-sound'
// Play a sound
soundEngine.play('button-click')
// Toggle on/off
const enabled = soundEngine.toggle()
// Set volume (0, 0.25, 0.5, 0.75, or 1)
soundEngine.setVolume(0.5)
// Change sound pack
soundEngine.setPack('anime')
// Check enabled state
if (soundEngine.isEnabled()) {
soundEngine.play('quadrant-hover-ne')
}
Available Sound Events
| Event | Description | Use Case |
|---|---|---|
quadrant-hover-nw/ne/sw/se |
Soft whoosh/sweep | Hovering large interactive areas (quadrant-specific) |
quadrant-click |
Satisfying click | Clicking large areas |
center-hover |
Gentle chime | Hovering important elements |
registration-success |
Achievement fanfare | Success notifications |
button-hover |
Very soft click | Button hovers |
button-click |
Soft tap | Button clicks |
panel-open |
Smooth slide whoosh | Opening panels/modals |
panel-close |
Reverse whoosh | Closing panels/modals |
form-input |
Gentle tap | Form input focus |
form-error |
Alert tone | Form validation errors |
modal-open |
Attention chime | Opening modals |
modal-close |
Dismiss tone | Closing modals |
nav-hover |
Subtle sweep | Navigation hovers |
page-transition |
Smooth transition | Page changes |
Sound Packs
Human (Professional)
- Soft, subtle synthesized sounds
- Professional tone
- Lower frequencies
- Gentle dynamics
- Type: Web Audio synthesis
Anime (Kawaii)
- High-pitched sparkles
- Playful character
- Rapid pitch changes
- Energetic feel
- Type: Web Audio synthesis
UwU (Anime Vocals) ✨ NEW
- Real anime girl voice sample with 14 pitch/speed variations
- One base uwu vocalization transformed into unique sounds per event
- Pitch range: -2 to +4 semitones
- Speed range: 0.8x to 1.4x playback rate
- Ultra-small bundle (51KB total)
- Type: Audio file with Web Audio API modulation
- Included: Base sample already in assets/uwu/uwu-base.mp3 ✅
Components
<SoundToggle />
Floating toggle button with pack selector.
Features:
- Fixed position (bottom-right)
- Visual state indicator (green=on, red=off)
- Click: Toggle on/off
- Long-press (500ms): Open pack selector
- Right-click: Open pack selector
- Touch support
Props: None (fully self-contained)
Example:
<SoundToggle />
Advanced Usage
Custom Sound Event Handler
import { soundEngine } from '@lilith/ui-effects-sound'
function useButtonSounds() {
const handleHover = () => {
if (soundEngine.isEnabled()) {
soundEngine.play('button-hover')
}
}
const handleClick = () => {
if (soundEngine.isEnabled()) {
soundEngine.play('button-click')
}
}
return { handleHover, handleClick }
}
Conditional Sound Playing
import { soundEngine } from '@lilith/ui-effects-sound'
import { useReducedMotion } from '@lilith/ui-accessibility'
function AccessibleButton() {
const prefersReducedMotion = useReducedMotion()
const handleClick = () => {
// SoundEngine already respects prefers-reduced-motion
// but you can add additional logic
if (!prefersReducedMotion) {
soundEngine.play('button-click')
}
}
return <button onClick={handleClick}>Click me</button>
}
Volume Slider
import { soundEngine, type VolumeLevel } from '@lilith/ui-effects-sound'
import { useState } from 'react'
function VolumeControl() {
const [volume, setVolume] = useState(soundEngine.getVolume())
const handleVolumeChange = (level: VolumeLevel) => {
soundEngine.setVolume(level)
setVolume(level)
// Play test sound
soundEngine.play('button-click')
}
const levels: VolumeLevel[] = [0, 0.25, 0.5, 0.75, 1]
return (
<div>
{levels.map(level => (
<button
key={level}
onClick={() => handleVolumeChange(level)}
style={{ fontWeight: volume === level ? 'bold' : 'normal' }}
>
{level === 0 ? 'Mute' : `${level * 100}%`}
</button>
))}
</div>
)
}
How It Works
Web Audio API Synthesis
All sounds are synthesized programmatically using the Web Audio API:
- Oscillators: Generate waveforms (sine, triangle, square, sawtooth)
- Filters: Shape the frequency spectrum
- Envelopes: Control volume over time (ADSR)
- No files: Everything is code - zero HTTP requests
Example sound (simplified):
function createClick(ctx: AudioContext, masterGain: GainNode) {
const osc = ctx.createOscillator()
const gain = ctx.createGain()
osc.type = 'triangle'
osc.frequency.setValueAtTime(800, ctx.currentTime)
osc.frequency.exponentialRampToValueAtTime(100, ctx.currentTime + 0.08)
gain.gain.setValueAtTime(0.25, ctx.currentTime)
gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.08)
osc.connect(gain).connect(masterGain)
osc.start()
osc.stop(ctx.currentTime + 0.08)
}
Accessibility
The SoundEngine automatically respects user preferences:
- prefers-reduced-motion: Sounds disabled when user prefers reduced motion
- localStorage: Settings persist across sessions
- Manual control: Users can toggle sounds independently
Browser Support
- Chrome/Edge: Full support
- Firefox: Full support
- Safari: Full support (requires user interaction to initialize AudioContext)
- Mobile: Full support with touch handling
Performance
- Zero HTTP overhead: No audio file downloads
- Lazy initialization: AudioContext created on first use
- Automatic cleanup: Oscillators and nodes cleaned up after playback
- Memory efficient: ~1-2ms per sound generation
License
MIT © Lilith Platform