Files
PTT-Live/client/src/components/PTTButton.css
T
benoit 7e42164c5c feat: intégration VU-mètre circulaire autour du bouton PTT
NOUVEAU DESIGN :
- VU-mètre transformé en anneau SVG autour du bouton PTT
- Cercle progressif (0-360°) selon le niveau audio
- Couleurs dynamiques selon le niveau :
  - Vert (0-75%) : audio normal
  - Orange (75-90%) : niveau élevé
  - Rouge (90-100%) : saturation/danger avec effet glow
  - Bleu : mode talking (micro actif)

AVANTAGES :
- Beaucoup plus discret et élégant
- Intégré visuellement au bouton principal
- Pas d'encombrement UI supplémentaire
- Contextuellement pertinent (niveau autour du contrôle)

TECHNIQUE :
- SVG avec stroke-dasharray pour l'arc progressif
- Transition fluide 0.1s sur le niveau
- Drop-shadow dynamique selon couleur
- Responsive (adapté mobile/paysage)
- z-index stratifié (anneau derrière, bouton devant)

SUPPRESSION :
- Ancien composant AudioIndicator.jsx retiré de App.jsx
- Garde plus d'espace pour UserList et GroupSelector

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

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-23 10:17:29 +02:00

280 lines
5.1 KiB
CSS

/* PTTButton - Bouton principal Push-To-Talk */
.ptt-container {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: var(--spacing-xl);
gap: var(--spacing-lg);
position: relative;
}
/* Wrapper bouton + anneau VU-mètre */
.ptt-button-wrapper {
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
/* Anneau VU-mètre (SVG) */
.audio-ring {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
z-index: 0;
}
.ptt-button {
width: 240px;
height: 240px;
border-radius: 50%;
background: var(--color-ptt-idle);
color: white;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--spacing-md);
cursor: pointer;
transition: transform 0.1s ease, box-shadow 0.2s ease, background 0.2s ease;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
position: relative;
overflow: hidden;
z-index: 1; /* Au-dessus de l'anneau */
/* Mobile touch optimizations */
touch-action: none;
-webkit-user-select: none;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
.ptt-button::before {
content: '';
position: absolute;
inset: 0;
border-radius: 50%;
background: radial-gradient(circle at center, rgba(255, 255, 255, 0.1), transparent);
opacity: 0;
transition: opacity 0.2s;
}
.ptt-button:active::before {
opacity: 1;
}
.ptt-button:active {
transform: scale(0.95);
}
/* État: En train de parler */
.ptt-button.talking {
background: var(--color-ptt-talking);
box-shadow: 0 8px 32px rgba(239, 68, 68, 0.5),
0 0 60px rgba(239, 68, 68, 0.3);
animation: pulse-talking 1.5s ease-in-out infinite;
}
/* État: Mode lock (continu) */
.ptt-button.locked {
background: linear-gradient(135deg, #dc2626 0%, #991b1b 100%);
box-shadow: 0 8px 32px rgba(220, 38, 38, 0.6),
0 0 80px rgba(220, 38, 38, 0.4);
animation: pulse-locked 2s ease-in-out infinite;
}
@keyframes pulse-talking {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
}
@keyframes pulse-locked {
0%, 100% {
transform: scale(1);
box-shadow: 0 8px 32px rgba(220, 38, 38, 0.6),
0 0 80px rgba(220, 38, 38, 0.4);
}
50% {
transform: scale(1.03);
box-shadow: 0 8px 40px rgba(220, 38, 38, 0.7),
0 0 100px rgba(220, 38, 38, 0.5);
}
}
/* Icône micro */
.ptt-icon {
width: 64px;
height: 64px;
color: white;
}
.ptt-icon svg {
width: 100%;
height: 100%;
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
}
/* Label */
.ptt-label {
font-size: 1rem;
font-weight: 600;
text-align: center;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
max-width: 180px;
}
/* Hint text */
.ptt-hint {
color: var(--color-text-secondary);
font-size: 0.9rem;
text-align: center;
max-width: 90%;
line-height: 1.4;
}
/* Indicateur de drag vers le haut */
.drag-indicator {
position: absolute;
top: -60px;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
color: #fbbf24;
font-size: 0.875rem;
font-weight: 600;
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
pointer-events: none;
z-index: 10;
animation: drag-pulse 0.6s ease-in-out infinite;
}
.drag-indicator svg {
filter: drop-shadow(0 2px 8px rgba(251, 191, 36, 0.6));
}
@keyframes drag-pulse {
0%, 100% {
transform: translateX(-50%) translateY(0);
}
50% {
transform: translateX(-50%) translateY(-5px);
}
}
/* Badge mode lock */
.lock-badge {
position: absolute;
top: 20px;
right: 20px;
width: 40px;
height: 40px;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(10px);
animation: lock-badge-pulse 1s ease-in-out infinite;
z-index: 2;
}
@keyframes lock-badge-pulse {
0%, 100% {
transform: scale(1);
background: rgba(255, 255, 255, 0.2);
}
50% {
transform: scale(1.1);
background: rgba(255, 255, 255, 0.3);
}
}
/* Responsive mobile */
@media (max-width: 640px) {
.ptt-button {
width: 200px;
height: 200px;
}
.audio-ring {
width: 240px;
height: 240px;
}
.ptt-icon {
width: 56px;
height: 56px;
}
.ptt-label {
font-size: 0.9rem;
}
}
/* Mode paysage */
@media (max-height: 500px) and (orientation: landscape) {
.ptt-container {
padding: var(--spacing-md);
}
.ptt-button {
width: 160px;
height: 160px;
}
.audio-ring {
width: 200px;
height: 200px;
}
.ptt-icon {
width: 48px;
height: 48px;
}
.ptt-label {
font-size: 0.8rem;
}
.ptt-hint {
font-size: 0.8rem;
}
}
/* Responsive mobile - badge lock */
@media (max-width: 640px) {
.lock-badge {
top: 15px;
right: 15px;
width: 36px;
height: 36px;
}
}
/* Accessibilité : désactiver effets réduits */
@media (prefers-reduced-motion: reduce) {
.ptt-button,
.ptt-button.talking,
.ptt-button.locked,
.lock-badge {
animation: none;
transition: none;
}
.lock-progress {
transition: none;
}
}