Files
AudiOhm/backend/app/templates/index.html
T
root 801e6a050b prod: UI Optimisée mise en production
- Documentation archivée et réorganisée
- Backend: Ajout tests, migrations, library service, rate limiting
- Frontend: Suppression Flutter, focus sur interface web HTML/JS
- Tailwind CSS ajouté pour le style
- Améliorations UX et corrections bugs

Generated with [Claude Code](https://claude.com/claude-code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-01-20 09:56:39 +00:00

783 lines
46 KiB
HTML

<!DOCTYPE html>
<html lang="fr" class="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AudiOhm - Web Player</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
colors: {
primary: {
50: '#f0f9ff',
100: '#e0f2fe',
200: '#bae6fd',
300: '#7dd3fc',
400: '#38bdf8',
500: '#0ea5e9',
600: '#0284c7',
700: '#0369a1',
800: '#075985',
900: '#0c4a6e',
},
accent: {
50: '#fdf2f8',
100: '#fce7f3',
200: '#fbcfe8',
300: '#f9a8d4',
400: '#f472b6',
500: '#ec4899',
600: '#db2777',
700: '#be185d',
800: '#9d174d',
900: '#831843',
},
success: '#10b981',
warning: '#f59e0b',
error: '#ef4444',
}
}
}
}
</script>
<style>
/* Custom animations */
@keyframes spin {
to { transform: rotate(360deg); }
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes slideIn {
from { transform: translateX(100%); }
to { transform: translateX(0); }
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.animate-spin {
animation: spin 1s linear infinite;
}
.animate-fadeIn {
animation: fadeIn 0.3s ease-out;
}
.animate-slideIn {
animation: slideIn 0.3s ease-out;
}
.animate-pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #1f2937;
}
::-webkit-scrollbar-thumb {
background: #4b5563;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #6b7280;
}
/* Glassmorphism */
.glass {
background: rgba(17, 24, 39, 0.8);
backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.glass-card {
background: rgba(31, 41, 55, 0.95);
backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
/* Range slider styling */
input[type="range"] {
-webkit-appearance: none;
appearance: none;
background: transparent;
cursor: pointer;
}
input[type="range"]::-webkit-slider-track {
background: #374151;
height: 4px;
border-radius: 2px;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
margin-top: -4px;
background-color: #0ea5e9;
height: 12px;
width: 12px;
border-radius: 50%;
transition: all 0.2s;
}
input[type="range"]::-webkit-slider-thumb:hover {
background-color: #38bdf8;
transform: scale(1.2);
}
/* Larger slider for desktop */
@media (min-width: 640px) {
input[type="range"]::-webkit-slider-track {
height: 6px;
border-radius: 3px;
}
input[type="range"]::-webkit-slider-thumb {
height: 14px;
width: 14px;
}
}
/* Screen reader only utility */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
.sr-only.focusable:focus {
position: static;
width: auto;
height: auto;
padding: inherit;
margin: inherit;
overflow: visible;
clip: auto;
white-space: normal;
}
/* Focus visible styles for keyboard navigation */
:focus-visible {
outline: 2px solid #0ea5e9;
outline-offset: 2px;
}
/* Better focus styles for interactive elements */
button:focus-visible,
a:focus-visible,
input:focus-visible,
[tabindex]:focus-visible {
outline: 2px solid #0ea5e9;
outline-offset: 2px;
}
/* Library Tabs Styles */
.library-tab {
background: rgba(31, 41, 55, 0.6);
color: #9ca3af;
border: 1px solid rgba(255, 255, 255, 0.08);
transition: all 0.2s;
}
.library-tab:hover {
background: rgba(55, 65, 81, 0.6);
color: #e5e7eb;
}
.library-tab.active {
background: rgba(14, 165, 233, 0.2);
color: #38bdf8;
border-color: rgba(56, 189, 248, 0.3);
}
.tab-panel {
display: none;
}
.tab-panel.active {
display: block;
}
</style>
</head>
<body class="bg-gradient-to-br from-gray-900 via-slate-900 to-gray-900 min-h-screen text-white font-sans">
<!-- Skip Link for Accessibility -->
<a href="#main-content" class="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:px-4 focus:py-2 focus:bg-primary-600 focus:text-white focus:rounded-lg">
Aller au contenu principal
</a>
<!-- Toast Container -->
<div id="toast-container" class="fixed top-4 right-4 z-50 flex flex-col gap-2" role="status" aria-live="polite" aria-atomic="true"></div>
<!-- App Container -->
<div id="app" class="min-h-screen">
<!-- Loading Screen -->
<div id="loading-screen" class="fixed inset-0 bg-gray-900 flex flex-col items-center justify-center z-50" role="status" aria-live="polite" aria-busy="true">
<div class="relative w-16 h-16 mb-6">
<div class="absolute inset-0 border-4 border-primary-500/30 rounded-full"></div>
<div class="absolute inset-0 border-4 border-transparent border-t-primary-500 rounded-full animate-spin"></div>
</div>
<h2 class="text-2xl font-bold bg-gradient-to-r from-primary-400 to-accent-400 bg-clip-text text-transparent">
Chargement de AudiOhm...
</h2>
<p class="text-gray-400 mt-2">Préparation de votre expérience musicale</p>
</div>
<!-- Login Screen -->
<div id="login-screen" class="hidden fixed inset-0 bg-gradient-to-br from-gray-900 via-slate-900 to-gray-900 flex items-center justify-center p-4" role="dialog" aria-modal="true" aria-labelledby="login-title">
<div class="glass-card rounded-2xl p-8 w-full max-w-md animate-fadeIn">
<!-- Logo -->
<div class="text-center mb-8">
<div class="inline-flex items-center justify-center w-20 h-20 rounded-2xl bg-gradient-to-br from-primary-500 to-accent-500 mb-4 shadow-lg shadow-primary-500/25" aria-hidden="true">
<i class="fas fa-headphones text-4xl text-white"></i>
</div>
<h1 id="login-title" class="text-3xl font-bold bg-gradient-to-r from-primary-400 to-accent-400 bg-clip-text text-transparent">
AudiOhm
</h1>
<p class="text-gray-400 mt-2">Votre musique, illimitée</p>
</div>
<!-- Login Form -->
<form id="login-form" class="space-y-4" aria-label="Formulaire de connexion">
<div>
<label for="login-email" class="block text-sm font-medium text-gray-300 mb-2">Email</label>
<div class="relative">
<i class="fas fa-envelope absolute left-3 top-1/2 -translate-y-1/2 text-gray-500" aria-hidden="true"></i>
<input type="email" id="login-email" required
class="w-full pl-10 pr-4 py-3 bg-gray-800/50 border border-gray-700 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-transparent focus:outline-none transition-all"
placeholder="vous@example.com" autocomplete="email" aria-describedby="login-email-hint">
</div>
</div>
<div>
<label for="login-password" class="block text-sm font-medium text-gray-300 mb-2">Mot de passe</label>
<div class="relative">
<i class="fas fa-lock absolute left-3 top-1/2 -translate-y-1/2 text-gray-500" aria-hidden="true"></i>
<input type="password" id="login-password" required
class="w-full pl-10 pr-4 py-3 bg-gray-800/50 border border-gray-700 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-transparent focus:outline-none transition-all"
placeholder="••••••••" autocomplete="current-password">
</div>
</div>
<button type="submit"
class="w-full py-3 px-4 bg-gradient-to-r from-primary-600 to-primary-500 hover:from-primary-500 hover:to-primary-400 rounded-xl font-semibold transition-all transform hover:scale-[1.02] active:scale-[0.98] focus:outline-none focus:ring-2 focus:ring-primary-400 focus:ring-offset-2 focus:ring-offset-gray-900 shadow-lg shadow-primary-500/25">
<i class="fas fa-sign-in-alt mr-2" aria-hidden="true"></i>
Se connecter
</button>
<div class="text-center">
<p class="text-gray-400 text-sm">
Pas encore de compte ?
<button type="button" id="show-register" class="text-primary-400 hover:text-primary-300 font-medium transition-colors focus:outline-none focus:underline focus:ring-2 focus:ring-primary-400 rounded">
Créer un compte
</button>
</p>
</div>
</form>
<!-- Register Form -->
<form id="register-form" class="hidden space-y-4" aria-label="Formulaire d'inscription">
<div>
<label for="register-username" class="block text-sm font-medium text-gray-300 mb-2">Nom d'utilisateur</label>
<div class="relative">
<i class="fas fa-user absolute left-3 top-1/2 -translate-y-1/2 text-gray-500" aria-hidden="true"></i>
<input type="text" id="register-username" required
class="w-full pl-10 pr-4 py-3 bg-gray-800/50 border border-gray-700 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-transparent focus:outline-none transition-all"
placeholder="votre_pseudo" autocomplete="username">
</div>
</div>
<div>
<label for="register-email" class="block text-sm font-medium text-gray-300 mb-2">Email</label>
<div class="relative">
<i class="fas fa-envelope absolute left-3 top-1/2 -translate-y-1/2 text-gray-500" aria-hidden="true"></i>
<input type="email" id="register-email" required
class="w-full pl-10 pr-4 py-3 bg-gray-800/50 border border-gray-700 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-transparent focus:outline-none transition-all"
placeholder="vous@example.com" autocomplete="email">
</div>
</div>
<div>
<label for="register-password" class="block text-sm font-medium text-gray-300 mb-2">Mot de passe</label>
<div class="relative">
<i class="fas fa-lock absolute left-3 top-1/2 -translate-y-1/2 text-gray-500" aria-hidden="true"></i>
<input type="password" id="register-password" required minlength="8"
class="w-full pl-10 pr-4 py-3 bg-gray-800/50 border border-gray-700 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-transparent focus:outline-none transition-all"
placeholder="Min. 8 caractères" autocomplete="new-password" aria-describedby="password-requirements">
</div>
</div>
<button type="submit"
class="w-full py-3 px-4 bg-gradient-to-r from-accent-600 to-accent-500 hover:from-accent-500 hover:to-accent-400 rounded-xl font-semibold transition-all transform hover:scale-[1.02] active:scale-[0.98] focus:outline-none focus:ring-2 focus:ring-accent-400 focus:ring-offset-2 focus:ring-offset-gray-900 shadow-lg shadow-accent-500/25">
<i class="fas fa-user-plus mr-2" aria-hidden="true"></i>
Créer un compte
</button>
<div class="text-center">
<p class="text-gray-400 text-sm">
Déjà un compte ?
<button type="button" id="show-login" class="text-accent-400 hover:text-accent-300 font-medium transition-colors focus:outline-none focus:underline focus:ring-2 focus:ring-accent-400 rounded">
Se connecter
</button>
</p>
</div>
</form>
<div id="auth-error" class="hidden mt-4 p-3 bg-red-500/10 border border-red-500/20 rounded-lg text-red-400 text-sm"></div>
</div>
</div>
<!-- Main App -->
<div id="main-app" class="hidden">
<!-- Mobile Menu Button -->
<button id="mobile-menu-btn" class="lg:hidden fixed top-4 left-4 z-40 p-3 glass rounded-xl hover:bg-gray-800/50 focus:outline-none focus:ring-2 focus:ring-primary-500 transition-all" aria-label="Ouvrir le menu" aria-expanded="false" aria-controls="sidebar">
<i class="fas fa-bars text-xl" aria-hidden="true"></i>
</button>
<!-- Sidebar -->
<aside id="sidebar" class="fixed left-0 top-0 h-full w-64 glass border-r border-gray-800 z-30 transform -translate-x-full lg:translate-x-0 transition-transform duration-300" aria-label="Navigation principale">
<div class="p-6">
<!-- Logo -->
<div class="flex items-center gap-3 mb-8">
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-primary-500 to-accent-500 flex items-center justify-center shadow-lg" aria-hidden="true">
<i class="fas fa-headphones text-white"></i>
</div>
<h1 class="text-xl font-bold">AudiOhm</h1>
</div>
<!-- Navigation -->
<nav class="space-y-2" aria-label="Navigation principale">
<a href="#" data-page="home" class="nav-item active flex items-center gap-3 px-4 py-3 rounded-xl bg-primary-500/10 text-primary-400 transition-all focus:outline-none focus:ring-2 focus:ring-primary-500" role="button" aria-current="page">
<i class="fas fa-home w-5" aria-hidden="true"></i>
<span>Accueil</span>
</a>
<a href="#" data-page="search" class="nav-item flex items-center gap-3 px-4 py-3 rounded-xl text-gray-400 hover:bg-gray-800/50 transition-all focus:outline-none focus:ring-2 focus:ring-primary-500" role="button">
<i class="fas fa-search w-5" aria-hidden="true"></i>
<span>Rechercher</span>
</a>
<a href="#" data-page="library" class="nav-item flex items-center gap-3 px-4 py-3 rounded-xl text-gray-400 hover:bg-gray-800/50 transition-all focus:outline-none focus:ring-2 focus:ring-primary-500" role="button">
<i class="fas fa-music w-5" aria-hidden="true"></i>
<span>Bibliothèque</span>
</a>
</nav>
</div>
<!-- Logout -->
<div class="absolute bottom-0 left-0 right-0 p-6 border-t border-gray-800">
<button id="logout-btn" class="w-full flex items-center justify-center gap-2 px-4 py-3 text-gray-400 hover:text-white hover:bg-gray-800/50 rounded-xl transition-all focus:outline-none focus:ring-2 focus:ring-accent-500" aria-label="Se déconnecter">
<i class="fas fa-sign-out-alt" aria-hidden="true"></i>
<span>Déconnexion</span>
</button>
</div>
</aside>
<!-- Main Content -->
<main id="main-content" class="lg:ml-64 min-h-screen pb-20 sm:pb-32" tabindex="-1">
<!-- Home Page -->
<div id="home-page" class="page active p-4 sm:p-6 lg:p-10 pt-16 sm:pt-6 lg:pt-10">
<!-- Header -->
<div class="mb-6 sm:mb-8">
<h1 class="text-2xl sm:text-3xl lg:text-4xl font-bold mb-2">
<span class="bg-gradient-to-r from-primary-400 to-accent-400 bg-clip-text text-transparent">
Bienvenue sur AudiOhm
</span>
<span class="text-xl sm:text-2xl"> 🎵</span>
</h1>
<p class="text-sm sm:text-base text-gray-400">Votre alternative à Spotify avec streaming YouTube</p>
</div>
<!-- Quick Search -->
<section class="mb-8 sm:mb-10" aria-labelledby="quick-search-heading">
<h2 id="quick-search-heading" class="text-lg sm:text-xl font-semibold mb-3 sm:mb-4 flex items-center gap-2">
<i class="fas fa-bolt text-primary-400" aria-hidden="true"></i>
Recherche rapide
</h2>
<div class="flex flex-col sm:flex-row gap-2 sm:gap-3">
<label for="quick-search" class="sr-only">Rechercher une musique</label>
<input type="search" id="quick-search"
class="flex-1 px-3 sm:px-4 py-2 sm:py-3 bg-gray-800/50 border border-gray-700 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-transparent focus:outline-none transition-all text-sm"
placeholder="Rechercher une musique, un artiste..." aria-describedby="quick-search-hint">
<button id="quick-search-btn" class="px-4 sm:px-6 py-2 sm:py-3 bg-primary-600 hover:bg-primary-500 rounded-xl font-medium transition-all focus:outline-none focus:ring-2 focus:ring-primary-400 focus:ring-offset-2 focus:ring-offset-gray-900 min-h-[44px] sm:min-h-[48px]" aria-label="Lancer la recherche">
<i class="fas fa-search text-sm sm:text-base" aria-hidden="true"></i>
<span class="sr-only">Rechercher</span>
</button>
</div>
</section>
<!-- Trending -->
<section class="mb-8 sm:mb-10">
<h2 class="text-lg sm:text-xl font-semibold mb-3 sm:mb-4 flex items-center gap-2">
<i class="fas fa-fire text-accent-400"></i>
Musiques tendance
</h2>
<div id="trending-tracks" class="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-3 sm:gap-4">
<div class="flex flex-col items-center justify-center py-16 sm:py-20">
<div class="w-10 h-10 sm:w-12 sm:h-12 border-4 border-primary-500/30 border-t-primary-500 rounded-full animate-spin mb-3 sm:mb-4"></div>
<p class="text-sm sm:text-base text-gray-400">Chargement...</p>
</div>
</div>
</section>
<!-- Recent -->
<section>
<h2 class="text-lg sm:text-xl font-semibold mb-3 sm:mb-4 flex items-center gap-2">
<i class="fas fa-clock text-primary-400"></i>
Récemment écoutées
</h2>
<div id="recent-tracks" class="text-sm sm:text-base text-gray-400">
<p>Aucune écoute récente</p>
</div>
</section>
</div>
<!-- Search Page -->
<div id="search-page" class="page hidden p-4 sm:p-6 lg:p-10 pt-16 sm:pt-6 lg:pt-10">
<h1 class="text-2xl sm:text-3xl font-bold mb-4 sm:mb-6 flex items-center gap-2 sm:gap-3">
<i class="fas fa-search text-primary-400" aria-hidden="true"></i>
Recherche
</h1>
<div class="flex flex-col sm:flex-row gap-2 sm:gap-3 mb-6 sm:mb-8">
<label for="search-input" class="sr-only">Rechercher de la musique</label>
<input type="search" id="search-input"
class="flex-1 px-3 sm:px-4 py-2 sm:py-3 bg-gray-800/50 border border-gray-700 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-transparent focus:outline-none transition-all text-sm"
placeholder="Que voulez-vous écouter ?" aria-describedby="search-hint">
<button id="search-btn" class="px-4 sm:px-8 py-2 sm:py-3 bg-gradient-to-r from-primary-600 to-primary-500 hover:from-primary-500 hover:to-primary-400 rounded-xl font-semibold transition-all focus:outline-none focus:ring-2 focus:ring-primary-400 focus:ring-offset-2 focus:ring-offset-gray-900 min-h-[44px] sm:min-h-[48px] text-sm sm:text-base">
<i class="fas fa-search mr-0 sm:mr-2" aria-hidden="true"></i>
<span class="hidden sm:inline">Rechercher</span>
</button>
</div>
<div id="search-results" role="list" aria-label="Résultats de recherche" class="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-3 sm:gap-4"></div>
</div>
<!-- Library Page -->
<div id="library-page" class="page hidden p-4 sm:p-6 lg:p-10 pt-16 sm:pt-6 lg:pt-10">
<h1 class="text-2xl sm:text-3xl font-bold mb-4 sm:mb-6 flex items-center gap-2 sm:gap-3">
<i class="fas fa-music text-accent-400"></i>
Ma Bibliothèque
</h1>
<!-- Tabs Navigation -->
<div class="flex gap-2 mb-6 overflow-x-auto pb-2" role="tablist" aria-label="Onglets de la bibliothèque">
<button id="tab-playlists" class="library-tab active px-4 py-2 rounded-lg font-medium transition-all whitespace-nowrap focus:outline-none focus:ring-2 focus:ring-primary-500"
role="tab"
aria-selected="true"
aria-controls="library-playlists"
onclick="switchLibraryTab('playlists')">
<i class="fas fa-list mr-2"></i>
<span class="hidden sm:inline">Playlists</span>
<span class="sm:hidden">Playlists</span>
</button>
<button id="tab-liked" class="library-tab px-4 py-2 rounded-lg font-medium transition-all whitespace-nowrap focus:outline-none focus:ring-2 focus:ring-primary-500"
role="tab"
aria-selected="false"
aria-controls="library-liked"
onclick="switchLibraryTab('liked')">
<i class="fas fa-heart mr-2"></i>
<span class="hidden sm:inline">Titres likés</span>
<span class="sm:hidden">Likés</span>
</button>
<button id="tab-history" class="library-tab px-4 py-2 rounded-lg font-medium transition-all whitespace-nowrap focus:outline-none focus:ring-2 focus:ring-primary-500"
role="tab"
aria-selected="false"
aria-controls="library-history"
onclick="switchLibraryTab('history')">
<i class="fas fa-history mr-2"></i>
<span class="hidden sm:inline">Historique</span>
<span class="sm:hidden">Historique</span>
</button>
</div>
<!-- Tab Panels -->
<div class="tab-panels">
<!-- Playlists Tab -->
<div id="library-playlists" class="tab-panel active" role="tabpanel" aria-labelledby="tab-playlists">
<section class="mb-8 sm:mb-10">
<div class="flex items-center justify-between mb-3 sm:mb-4">
<h2 class="text-lg sm:text-xl font-semibold flex items-center gap-2">
<i class="fas fa-list text-primary-400"></i>
Mes Playlists
</h2>
<button id="create-playlist-btn" class="px-3 sm:px-4 py-2 bg-primary-600 hover:bg-primary-500 rounded-lg font-medium transition-all text-sm flex items-center gap-2 focus:outline-none focus:ring-2 focus:ring-primary-400" aria-label="Créer une nouvelle playlist">
<i class="fas fa-plus"></i>
<span class="hidden sm:inline">Créer</span>
</button>
</div>
<div id="my-playlists" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4">
<div class="flex flex-col items-center justify-center py-16 sm:py-20">
<div class="w-10 h-10 sm:w-12 sm:h-12 border-4 border-primary-500/30 border-t-primary-500 rounded-full animate-spin mb-3 sm:mb-4"></div>
<p class="text-sm sm:text-base text-gray-400">Chargement...</p>
</div>
</div>
</section>
</div>
<!-- Liked Tracks Tab -->
<div id="library-liked" class="tab-panel hidden" role="tabpanel" aria-labelledby="tab-liked">
<section>
<h2 class="text-lg sm:text-xl font-semibold mb-3 sm:mb-4 flex items-center gap-2">
<i class="fas fa-heart text-accent-400"></i>
Titres likés
</h2>
<div id="liked-tracks" class="space-y-2 max-w-4xl">
<div class="flex flex-col items-center justify-center py-16 sm:py-20">
<div class="w-10 h-10 sm:w-12 sm:h-12 border-4 border-accent-500/30 border-t-accent-500 rounded-full animate-spin mb-3 sm:mb-4"></div>
<p class="text-sm sm:text-base text-gray-400">Chargement...</p>
</div>
</div>
</section>
</div>
<!-- Listening History Tab -->
<div id="library-history" class="tab-panel hidden" role="tabpanel" aria-labelledby="tab-history">
<section>
<h2 class="text-lg sm:text-xl font-semibold mb-3 sm:mb-4 flex items-center gap-2">
<i class="fas fa-history text-primary-400"></i>
Historique d'écoute
</h2>
<div id="listening-history" class="space-y-2 max-w-4xl">
<div class="flex flex-col items-center justify-center py-16 sm:py-20">
<div class="w-10 h-10 sm:w-12 sm:h-12 border-4 border-primary-500/30 border-t-primary-500 rounded-full animate-spin mb-3 sm:mb-4"></div>
<p class="text-sm sm:text-base text-gray-400">Chargement...</p>
</div>
</div>
</section>
</div>
</div>
</div>
</main>
</div>
<!-- Player -->
<div id="player" class="hidden fixed bottom-0 left-0 right-0 glass border-t border-gray-800 px-2 sm:px-4 py-2 sm:py-3 z-40" role="region" aria-label="Lecteur audio">
<!-- Mobile Compact View -->
<div class="sm:hidden flex items-center gap-2">
<!-- Track Info (Mobile) -->
<div class="flex items-center gap-2 flex-1 min-w-0">
<img id="player-cover" src="/static/img/default-cover.png" alt=""
class="w-10 h-10 rounded-lg object-cover bg-gray-800 flex-shrink-0" aria-hidden="true">
<button id="mobile-play-btn" class="p-2 bg-primary-600 rounded-full flex-shrink-0" aria-label="Lecture/Pause">
<i class="fas fa-play text-xs"></i>
</button>
<div class="min-w-0 flex-1">
<div id="player-title" class="font-medium text-xs truncate" aria-live="polite">Aucun titre</div>
<div id="player-artist" class="text-xs text-gray-400 truncate" aria-live="polite">-</div>
</div>
</div>
<!-- Actions (Mobile) -->
<div class="flex items-center gap-1 flex-shrink-0">
<button id="mobile-like-btn" class="p-2 text-gray-400 hover:text-accent-400 transition-all" aria-label="J'aime">
<i class="far fa-heart text-sm"></i>
</button>
<button id="mobile-expand-btn" class="p-2 text-gray-400 hover:text-white transition-all" aria-label="Agrandir le player">
<i class="fas fa-chevron-up text-sm"></i>
</button>
</div>
</div>
<!-- Desktop Full View -->
<div class="hidden sm:flex items-center gap-2 lg:gap-4 max-w-screen-2xl mx-auto">
<!-- Track Info -->
<div class="flex items-center gap-2 lg:gap-3 flex-shrink-0 w-32 lg:w-64">
<img id="player-cover-desktop" src="/static/img/default-cover.png" alt=""
class="w-10 h-10 lg:w-14 lg:h-14 rounded-lg object-cover bg-gray-800 flex-shrink-0" aria-hidden="true">
<div class="min-w-0 flex-1 hidden sm:block">
<div id="player-title-desktop" class="font-medium text-xs lg:text-sm truncate" aria-live="polite">Aucun titre</div>
<div id="player-artist-desktop" class="text-xs text-gray-400 truncate" aria-live="polite">-</div>
</div>
</div>
<!-- Controls -->
<div class="flex-1 flex flex-col items-center gap-1 lg:gap-2">
<!-- Main Controls -->
<div class="flex items-center gap-1 lg:gap-2">
<button id="shuffle-btn" class="p-1.5 lg:p-3 text-gray-400 hover:text-white hover:bg-gray-800/50 rounded-lg transition-all focus:outline-none focus:ring-2 focus:ring-primary-500 min-w-[36px] lg:min-w-[44px] min-h-[36px] lg:min-h-[44px] flex items-center justify-center" aria-label="Mode aléatoire" aria-pressed="false">
<i class="fas fa-random text-sm lg:text-base" aria-hidden="true"></i>
</button>
<button id="prev-btn" class="p-1.5 lg:p-3 text-gray-400 hover:text-white hover:bg-gray-800/50 rounded-lg transition-all focus:outline-none focus:ring-2 focus:ring-primary-500 min-w-[36px] lg:min-w-[44px] min-h-[36px] lg:min-h-[44px] flex items-center justify-center" aria-label="Piste précédente">
<i class="fas fa-step-backward text-sm lg:text-base" aria-hidden="true"></i>
</button>
<button id="play-btn" class="p-2 lg:p-4 bg-primary-600 hover:bg-primary-500 rounded-full transition-all transform hover:scale-110 focus:outline-none focus:ring-4 focus:ring-primary-500/50 min-w-[40px] lg:min-w-[52px] min-h-[40px] lg:min-h-[52px] flex items-center justify-center" aria-label="Lecture" aria-pressed="false">
<i class="fas fa-play text-sm lg:text-base" aria-hidden="true"></i>
</button>
<button id="next-btn" class="p-1.5 lg:p-3 text-gray-400 hover:text-white hover:bg-gray-800/50 rounded-lg transition-all focus:outline-none focus:ring-2 focus:ring-primary-500 min-w-[36px] lg:min-w-[44px] min-h-[36px] lg:min-h-[44px] flex items-center justify-center" aria-label="Piste suivante">
<i class="fas fa-step-forward text-sm lg:text-base" aria-hidden="true"></i>
</button>
<button id="repeat-btn" class="p-1.5 lg:p-3 text-gray-400 hover:text-white hover:bg-gray-800/50 rounded-lg transition-all focus:outline-none focus:ring-2 focus:ring-primary-500 min-w-[36px] lg:min-w-[44px] min-h-[36px] lg:min-h-[44px] flex items-center justify-center" aria-label="Répéter" aria-pressed="false">
<i class="fas fa-redo text-sm lg:text-base" aria-hidden="true"></i>
</button>
</div>
<!-- Progress -->
<div class="flex items-center gap-2 lg:gap-3 w-full max-w-xl px-2">
<span id="current-time" class="text-xs text-gray-400 w-8 lg:w-10 text-right flex-shrink-0" aria-live="off" aria-label="Temps écoulé">0:00</span>
<label for="progress-bar" class="sr-only">Barre de progression</label>
<input type="range" id="progress-bar" min="0" max="100" value="0"
class="flex-1 h-1" aria-label="Progression de la lecture" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" aria-valuetext="0%">
<span id="total-time" class="text-xs text-gray-400 w-8 lg:w-10 flex-shrink-0" aria-live="off" aria-label="Durée totale">0:00</span>
</div>
</div>
<!-- Volume & Actions -->
<div class="flex items-center gap-1 lg:gap-2 flex-shrink-0">
<button id="mute-btn" class="p-1.5 lg:p-3 text-gray-400 hover:text-white transition-all focus:outline-none focus:ring-2 focus:ring-primary-500 rounded-lg min-w-[36px] lg:min-w-[44px] min-h-[36px] lg:min-h-[44px] flex items-center justify-center" aria-label="Couper le son" aria-pressed="false">
<i class="fas fa-volume-up text-sm lg:text-base" aria-hidden="true"></i>
</button>
<label for="volume-bar" class="sr-only">Volume</label>
<input type="range" id="volume-bar" min="0" max="100" value="100"
class="w-12 lg:w-20 hidden md:block" aria-label="Volume" aria-valuemin="0" aria-valuemax="100" aria-valuenow="100" aria-valuetext="100%">
<div class="w-px h-6 lg:h-8 bg-gray-700 mx-1 lg:mx-2 hidden md:block" aria-hidden="true"></div>
<button id="like-btn" class="p-1.5 lg:p-3 text-gray-400 hover:text-accent-400 transition-all focus:outline-none focus:ring-2 focus:ring-accent-500 rounded-lg min-w-[36px] lg:min-w-[44px] min-h-[36px] lg:min-h-[44px] flex items-center justify-center" aria-label="J'aime" aria-pressed="false">
<i class="far fa-heart text-sm lg:text-base" aria-hidden="true"></i>
</button>
<button id="queue-open-btn" class="p-1.5 lg:p-3 text-gray-400 hover:text-white transition-all focus:outline-none focus:ring-2 focus:ring-primary-500 rounded-lg min-w-[36px] lg:min-w-[44px] min-h-[36px] lg:min-h-[44px] flex items-center justify-center relative" aria-label="File d'attente" aria-expanded="false">
<i class="fas fa-list-ul text-sm lg:text-base" aria-hidden="true"></i>
<span id="queue-count" class="absolute -top-1 -right-1 bg-primary-600 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center font-bold">0</span>
</button>
</div>
</div>
<audio id="audio-player" preload="none" class="hidden"></audio>
</div>
<!-- Create Playlist Modal -->
<div id="create-playlist-modal" class="hidden fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4" role="dialog" aria-labelledby="create-playlist-title" aria-modal="true" aria-hidden="true">
<div class="glass-card rounded-2xl p-6 w-full max-w-md animate-fadeIn">
<div class="flex items-center justify-between mb-6">
<h2 id="create-playlist-title" class="text-xl font-bold">Créer une playlist</h2>
<button id="close-create-playlist-modal" class="p-2 text-gray-400 hover:text-white transition-all rounded-lg hover:bg-gray-700/50 focus:outline-none focus:ring-2 focus:ring-primary-500" aria-label="Fermer">
<i class="fas fa-times"></i>
</button>
</div>
<form id="create-playlist-form" aria-label="Formulaire de création de playlist">
<div class="mb-4">
<label for="playlist-name" class="block text-sm font-medium text-gray-300 mb-2">Nom de la playlist *</label>
<input type="text" id="playlist-name" required
class="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-transparent focus:outline-none transition-all"
placeholder="Ma nouvelle playlist" aria-describedby="playlist-name-hint">
</div>
<div class="mb-6">
<label for="playlist-description" class="block text-sm font-medium text-gray-300 mb-2">Description (optionnel)</label>
<textarea id="playlist-description" rows="3"
class="w-full px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-transparent focus:outline-none transition-all resize-none"
placeholder="Décrivez votre playlist..."></textarea>
</div>
<div class="flex gap-3">
<button type="button" id="cancel-create-playlist"
class="flex-1 px-4 py-3 bg-gray-700 hover:bg-gray-600 rounded-xl font-medium transition-all focus:outline-none focus:ring-2 focus:ring-gray-500">
Annuler
</button>
<button type="submit"
class="flex-1 px-4 py-3 bg-gradient-to-r from-primary-600 to-primary-500 hover:from-primary-500 hover:to-primary-400 rounded-xl font-semibold transition-all transform hover:scale-[1.02] active:scale-[0.98] focus:outline-none focus:ring-2 focus:ring-primary-400 shadow-lg">
Créer
</button>
</div>
</form>
</div>
</div>
<!-- Playlist Details Modal -->
<div id="playlist-details-modal" class="hidden fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4" role="dialog" aria-labelledby="playlist-details-title" aria-modal="true" aria-hidden="true">
<div class="glass-card rounded-2xl w-full max-w-3xl max-h-[90vh] overflow-hidden animate-fadeIn flex flex-col">
<!-- Header -->
<div class="p-6 border-b border-gray-800">
<div class="flex items-center justify-between mb-4">
<h2 id="playlist-details-title" class="text-xl font-bold truncate flex-1">Titre de la playlist</h2>
<button id="close-playlist-details" class="p-2 text-gray-400 hover:text-white transition-all rounded-lg hover:bg-gray-700/50 focus:outline-none focus:ring-2 focus:ring-primary-500 ml-2" aria-label="Fermer">
<i class="fas fa-times"></i>
</button>
</div>
<p id="playlist-details-description" class="text-gray-400 text-sm mb-4"></p>
<div class="flex items-center gap-3">
<button id="play-playlist-btn" class="px-4 py-2 bg-primary-600 hover:bg-primary-500 rounded-lg font-medium transition-all flex items-center gap-2 focus:outline-none focus:ring-2 focus:ring-primary-400">
<i class="fas fa-play"></i>
Lecture
</button>
<button id="shuffle-playlist-btn" class="px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg font-medium transition-all flex items-center gap-2 focus:outline-none focus:ring-2 focus:ring-gray-500">
<i class="fas fa-random"></i>
Aléatoire
</button>
</div>
</div>
<!-- Tracks -->
<div id="playlist-tracks" class="flex-1 overflow-y-auto p-4">
<div class="flex flex-col items-center justify-center py-12 text-gray-400">
<i class="fas fa-music text-4xl mb-4"></i>
<p class="text-lg">Aucune piste</p>
</div>
</div>
</div>
</div>
<!-- Queue Panel -->
<div id="queue-panel" class="fixed inset-y-0 right-0 w-full sm:w-96 glass border-l border-gray-800 z-50 transform translate-x-full transition-transform duration-300 ease-out" role="dialog" aria-labelledby="queue-title" aria-hidden="true">
<!-- Header -->
<div class="p-4 sm:p-6 border-b border-gray-800">
<div class="flex items-center justify-between mb-4">
<h2 id="queue-title" class="text-lg sm:text-xl font-bold flex items-center gap-2">
<i class="fas fa-list-ul text-primary-400"></i>
File d'attente
<span id="queue-count-badge" class="text-sm font-normal text-gray-400">(0)</span>
</h2>
<button id="queue-close-btn" class="p-2 text-gray-400 hover:text-white transition-all focus:outline-none focus:ring-2 focus:ring-primary-500 rounded-lg" aria-label="Fermer la file d'attente">
<i class="fas fa-times text-lg"></i>
</button>
</div>
<!-- Queue Actions -->
<div class="flex items-center gap-2">
<button id="queue-shuffle-btn" class="flex-1 px-4 py-2 bg-gray-800/50 hover:bg-gray-700/50 text-gray-300 hover:text-white rounded-lg transition-all text-sm font-medium focus:outline-none focus:ring-2 focus:ring-primary-500 flex items-center justify-center gap-2" aria-label="Mélanger la file d'attente">
<i class="fas fa-random"></i>
Mélanger
</button>
<button id="queue-clear-btn" class="flex-1 px-4 py-2 bg-gray-800/50 hover:bg-red-600/30 text-gray-300 hover:text-red-400 rounded-lg transition-all text-sm font-medium focus:outline-none focus:ring-2 focus:ring-red-500 flex items-center justify-center gap-2" aria-label="Vider la file d'attente">
<i class="fas fa-trash-alt"></i>
Vider
</button>
</div>
</div>
<!-- Queue List -->
<div id="queue-list" class="p-4 overflow-y-auto" style="max-height: calc(100vh - 200px);" role="list" aria-label="Pistes dans la file d'attente">
<!-- Queue items will be dynamically inserted here -->
<div class="flex flex-col items-center justify-center py-12 text-gray-400">
<i class="fas fa-list-ul text-4xl mb-4"></i>
<p class="text-lg">File d'attente vide</p>
<p class="text-sm mt-2">Cliquez sur une piste pour l'ajouter</p>
</div>
</div>
</div>
</div>
<script src="/static/js/app.js"></script>
</body>
</html>