feat: implement complete React PWA client with LiveKit integration

Client React complet avec intégration LiveKit et interface PTT professionnelle :

Infrastructure :
- Configuration Vite avec plugin PWA (Service Worker auto-généré)
- Proxy API vers serveur backend
- Build optimisé et PWA manifest

Composants UI :
- App.jsx : écran connexion + interface principale PTT
- PTTButton : bouton push-to-talk avec gestion touch/mouse events
- UserList : liste participants temps réel avec indicateurs
- AudioIndicator : VU-mètre avec visualisation niveau audio

Fonctionnalités WebRTC :
- Hook useLiveKit : connexion room, publish/subscribe, events
- Gestion micro avec mute/unmute (mode PTT)
- Auto-play audio participants distants
- Analyseur audio pour VU-mètre
- Feedback haptique (vibrations)

Design :
- Mode sombre par défaut
- Responsive mobile-first
- Animations fluides et accessibles
- Support paysage mobile

Phase 1.4 complétée : Client PWA opérationnel

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-05-21 14:48:18 +02:00
parent 5e74f0dcdf
commit 0640a9f0b6
15 changed files with 1568 additions and 32 deletions
+150
View File
@@ -0,0 +1,150 @@
/* UserList - Liste des participants */
.user-list {
background: var(--color-surface);
border-bottom: 1px solid var(--color-border);
max-height: 180px;
overflow-y: auto;
}
.user-list.empty {
display: flex;
align-items: center;
justify-content: center;
padding: var(--spacing-lg);
max-height: none;
}
.empty-message {
color: var(--color-text-secondary);
font-size: 0.9rem;
}
.user-list-header {
padding: var(--spacing-sm) var(--spacing-lg);
background: var(--color-bg);
border-bottom: 1px solid var(--color-border);
position: sticky;
top: 0;
z-index: 1;
}
.user-count {
font-size: 0.85rem;
color: var(--color-text-secondary);
font-weight: 500;
}
.user-list-items {
padding: var(--spacing-sm);
}
/* Item utilisateur */
.user-item {
display: flex;
align-items: center;
gap: var(--spacing-md);
padding: var(--spacing-sm) var(--spacing-md);
border-radius: 8px;
transition: background 0.15s;
}
.user-item:hover {
background: var(--color-surface-hover);
}
.user-item.speaking {
background: rgba(16, 185, 129, 0.1);
}
/* Avatar */
.user-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--color-primary);
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 1.1rem;
flex-shrink: 0;
}
.user-item.speaking .user-avatar {
background: var(--color-success);
}
/* Info */
.user-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.user-name {
font-weight: 500;
font-size: 0.95rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.user-status {
font-size: 0.8rem;
color: var(--color-success);
font-weight: 500;
}
/* Indicateurs */
.user-indicator {
width: 24px;
height: 24px;
flex-shrink: 0;
}
.speaking-indicator {
width: 100%;
height: 100%;
color: var(--color-success);
}
.speaking-indicator svg {
width: 100%;
height: 100%;
}
.audio-indicator {
width: 100%;
height: 100%;
color: var(--color-text-secondary);
opacity: 0.5;
}
.audio-indicator svg {
width: 100%;
height: 100%;
}
/* Responsive */
@media (max-width: 640px) {
.user-list {
max-height: 150px;
}
.user-avatar {
width: 36px;
height: 36px;
font-size: 1rem;
}
.user-name {
font-size: 0.9rem;
}
.user-status {
font-size: 0.75rem;
}
}